ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 다시 C++ 정리
    @ 16. 1 ~ 17. 1/C++ 2016. 11. 15. 01:58

    프로그래밍 디자인(디자인하느라 하루종일 투자해도된다)
    ---------------------------------------------------------------------------------------------------------------------------------------------------------

    1. 공통적인 부분을 우선 모아보기
    2. 추상화~~ 인터페이스 나누기

    서브시스템 확인하기..서브 시스템이란..예를들어 최상위 객체? mgr개념의 클래스와 일반 객체 클래스까지 포함하는 최상위 클래스를 기능별로 나눈것..

    그리고 각 서브시스템의 인터페이스와 상호연동을 정의하는 것이다.

    표로 만든다면
    서브시스템 이름 | 인스턴스 개수 | 서브시스템 기능 |  공개된 인터페이스 | 어떤 서브시스템의 사용할 인터페이스

    그리고 스레드의 개수와 각 스레드간 연동 내용을 정해야하는데..
    스레드간 데이터 공유를 가능한 피해야한다..공유를 피하지 못한다면 스레드락 메커니즘을 사용해야한다.
    예를들어 그래픽 유저 인터페이스는 한 스레드에 주 작업을 맡기고
    다른 스레드는 사용자의 버튼 클릭이나 메뉴 선택과 같은 사용자 입력을 대기하도록 만들때가 많다.

    그다음 각 서브시스템의 세부적 부분을 검토하는데..
    서브시스템 | 클래스(대부분 서브시스템 항목자체가 클래스가 될때가 많다. 하위 클래스 작성) | 데이터 구조 | 알고리즘 | 디자인패턴


    함수는 한가지 기능을 하는게 원칙이다.
    먼저 A를 하고 다음에 B를 하고 만약 C이면 D를 하고 이런식이 아니다..

    ---------------------------------------------------------------------------------------------------------------------------------------------------------


    일반 프로그래밍
    ---------------------------------------------------------------------------------------------------------------------------------------------------------

    세단계의 빌드과정

    전처리 : 메타정보를 인식 자동으로 코드를 수정

    컴파일작업 : 소스 코드를 기계어로 번역

    링크작업 : 기계어로 된 여러 오브젝트 파일을 묶어서 하나의 실행파일로 합침



    현재 실행중인 함수 foo()가 또 다른 함수 bar()를 호출한다면 새로운 스택 프레임이 생성되고 

    여기서 스택프레임 관련..돌아갈 주소를 스택에 기록한다라..

    스택 영역에서 가리키고 있는 위치를  기억하는 레지스터는 총 32비츠 4바이트 크기이다. Stack Pointer (ESP)

    main함수 입장에서는 다른 함수의 위치를 알수있다 그래서 돌아갈 수 있는 주소를 저장하는데..

    이는 컴파일 당시에 지정이 되니까 가능한거임..결국...가변 크기의 배열을 함수의 로컬변수로 선언할 수 없다는것이다..

    http://blog.naver.com/PostView.nhn?blogId=hermet&logNo=56227646 참고



    OOP : (OBJECT ORIENTED PROGRAMMING)


    빅오 표기법은 데이터가 없거나 실행 소요 시간이 무작위적인 알고리즘은 적용할 수가 없다.
    빅오 에서 O( ) 괄호 안의 내용은 시간을 나타내는것..

    종종 통계적 기대 값을 반영하기도 한다.
    예를 들어 선형 탐색 알고리즘은 종종 O(n / 2)이라고 하는데..(그냥 기대값 없다면 o(n)이 맞다)
    왜냐면 종종 절반 정도를 탐색하면 값을 발견하니까..O(n/2)보다 더 빨리 찾아지는 경우들은 O(n/2)보다 더 오래 걸리는 경우들과 서로 상쇄되어 진다.

    C, C++에서는 타입 지정없이 void* 포인터를 이용해서 데이터 타입에 독립적인 코드를 작성할 순 있지만, 타입이 세이프하지 않다.
    의도했던 타입이 아니라면 큰일난다. 하지만 템플릿은 세이프하다. 그런데 템플릿도 한 데이터 구조에 한 타입의 객체만 저장할 수 있다..

    템플릿이 필요한 이유
    서로 다른 타입에 대해 같은 기능을 제공하길 원한다면 템플릿이 유리하다. 예를 들어 어떤 데이터 타입에도 적용될 수 있는 범용적 알고리즘이라면 더더욱....
    만약 타입에 따라서 서로 다른 동작을 해야한다면 상속을 이용한다.


    보통은 대입연산자 작성시에 반환값이 참조형태로 되는데 그 이유는 오버헤드도 피하면서 반드시 리턴값이 있어야만 하니까 그런것..
    직접 만든 자식 복사생성자에서 이니셜라이저에서 부모객체에 대한 복사생성자를 호출하지 않으면 그 부모디폴트 생성자가 호출이 되는데
    그 부모객체는 이미 디폴트 생성자로 초기화된 상태이다.


    다중상속이 왜 위험한가?

    왜? 인가
    1. 복수의 클래스로부터 같은 메서드가 있을때 상속받는 클래스에서 만약 그 함수를 호출할때 단순히 호출하게 되면 어떤걸 호출해야할지 몰라서 컴파일 에러가 난다.(호출하지않는한 문제는 발생안된다) 멤버변수도 마찬가지임..
    해결책은 스코프 연산자를 이용하면 해결은 된다.

    2. A클래스를 B클래스가 상속받고 C클래스에서 A클래스와 B클래스를 상속받게 되면(같은 클래스를 중복 상속받음) 컴파일 에러난다. 모호성떄문에 어떤 A클래스를 사용할지 몰라서..

    (아래는 다이아 몬드 형태라고들 하지요?)
    3. 부모클래스들이 같은 부모를 공통으로 가지는 경우.. 만약 부모의 부모클래스에 어떠한 함수가 있다면 그 함수는 부모의 부모를 상속받는(마지막 자식 클래스겠지..) 클래스에서 호출될 수가 없다. 어떤 부모에서 호출할지 모르기 때문에.. 해결책은 부모의 부모클래스에서 그 함수를 추상함수로 만든다. (추상클래스로 만들면 된다.)
    그리고 호출할때 스코프 연산자로 호출해야한다. 



    ---------------------------------------------------------------------------------------------------------------------------------------------------------

    C++ 98
    ---------------------------------------------------------------------------------------------------------------------------------------------------------

    #define key value : 앞으로 key는 value로 치환한다.(보통 #define대신에 const 붙인 변수를 이용하는게...)    


    char arrayString[20] = "Hello World";

    const char* pointerString = "Hello World";

    이 둘은 저장 공간이 다르다 맨 처음것은

    메모리가 스택에 할당되었고 두번째것은 포인터 변수를 저장할 공간만 스택에 할당되고 실제 문자열이 담길 메모리는 별도의

    문자열 상수 저장 영역에 할당하게 된다. 


    exception 내용인데..

    던진다는 throw 받는건 catch(받는건 에러 이후에 처리한다고 생각하면됨)

    예외가 발생될 만한 곳에

    throw std::exception 해버리면 함수의 실행이 즉시 중단된다. 물론..

    이상한 함수를 try의 스코프로 감싸고 (if ~ else와 같은 스코프 역할..)마지막에 catch스코프를 열어서 예외처리하면됨.

    try{

    무슨함수(함수 내에서 throw std::exception

    }

    catch{

    냠냠 이건 에러다 에러는 여기서 처리할테야

    }

    이런식으로 처리

    실제로 throw할때는 스택 변수에 무슨일이 일어나는지 정확히 알아야 한다..



    보통 참조형 파라미터(참조형 함수 매개변수) & 이건 변수의 변수값을 변경하려는 목적인데..const을 붙이는 경우가 있다.

    그이유는? 보통은 참조형 매개변수 쓰는이유가 성능 관점에서 볼때 불필요한 값의 복제를 피하기 위함인데..

    그런데 왜 const붙이냐? 그 이유는 오버헤드도 피하고 값 변조도 막으려고...후훗..


    멤버변수의 변경이 없는 함수는 함수 정의 끝에  const를 붙여주는 습관을 가지자..(void 함수() const 이런식이겠지..)


    함수의 매개변수가 *인데 포인터를 받아서 내부에서 delete 해버리면?

    댕글리 포인터가 된다. 왜냐면 매개변수에서 넘어온건 포인터의 포인터변수가 복사되서 넘어온거니까..

    같은곳을 2개가 바라보게 된다(함수에서 넘어온거, 함수에서 넘어오기 이전것) 그런데 한곳에서 그 바라본 메모리를 해제하니까

    날라가는것임..

    (내블로그에서 그냥 포인터로 검색하고 제목에 16일차 댕글리 포인터를 보면된다..)(댕글리 참조도 있다..개념은 똑같음..)


    생성자 내부에서 또 다른 생성자를 호출한다면? 이건 로컬 변수처럼 임시 스택 객체를 만드는것이다.


    디폴트 생성자가 없으면 클래스에 대해서 객체 배열을 생성할 수 없다.

    그리고 디폴트 생성자로 만든답시고 CTestClass Test(); 이렇게 만들어 버리면 -> () 추가된것

    그냥 컴파일러는 CTestClass를 반환값으로 가지는 Test()의 함수를 선언한거로 확인한다.

    그리고 그 이후에 Test라는 인스턴스로 접근은 당연히 에러다. 참고로 new 로 힙할당할때는 () 해도 상관없다.


    이니셜라이저 관련

    이니셜라이저로 초기화 하는 리스트에 등장하는 순서로 초기화되는게 아니라, 실제 맴버들이 등장한 순서로 초기화 된다.

    등장한 순서란 위에서 아래순이다. 위가 높은 등장 순서임..


    전방선언의 본래뜻은 미리 예고하여 참조한다.


    static 함수를 선언하려면 const 를 빼야한다. 왜냐면 객체에 묶이지 않기 때문에..

    this포인터도 이용못하고 연결된 객체가 없으니까...그러나 멤버함수내 함수호출로 사용은 가능하다.


    메서드나 함수를 별도의 분리된 코드 블록으로 호출하는 대신, 출 지점에 바로 메서드나 함수의 바디를 옮겨 넣어 호출 오버헤드를 줄이는 방법을 inline이라고 한다.

    컴파일러가 함수 호출 대신 그 바디를 삽입할 수 있으려면 당연히 그 바디의 내용을 해당 코드의 컴파일 시점에 가지고 있어야 한다.

    그래서 inline함수는 cpp파일이 아닌 h에 있게 된다...


    explict 암묵적인 변환 막는 키워드

    어..그러니까...임시객체를 막자는것...컴파일러가 생성 가능한 임시객체여부를 판단하고 그걸 수행하는데..그걸 막는거임..

    정확하게 캐스팅을 써야한다고..괄호를 하던 staic_cast를 하던..등등//

    클래스 정의부에서만 선언되고 (가장 앞에) 파라미터가 하나뿐인 생성자에서만 의미가 있다..???



    생성순서 : 부모 생성자 -> static 이 아닌 클래스 멤버변수 -> 해당 생성자 바디

    소멸순서 : 클래스 소멸자 바디 -> 데이터 멤버를 생선 순서와 반대로 삭제(밑에서 위로) -> 부모 클래스 소멸자 호출


    !!오버라이딩 함수의 리턴 타입변경!!

    보통은 메서드 원형과 완벽히 같게 오버라이딩하는게 기본이다. 즉, 구현내용은 바뀌어도 메서드의 원형은 바뀌지 않는다.

    그러나, 원본 메서드의 리턴 타입이 어떤 클래스에 대한 포인터나 참조형이라면 오버라이딩하는 메서드의 리턴 타입을 원본 리턴 타입 클래스의 서브클래스에 한하여 바꿀 수 있다. 이런걸 공변 리턴 타입이라고 한다.

    그러니까..부모* 함수명() 뭐 이런걸 자식* 함수명() 이런게 된다고..

    **전혀 관계없는 void* 이런건 안된다.**


    static 메서드를 오버라이딩 할 수 없다.

    1. 메서드가 static이면서 동시에 virtual 일수는 없다. 만약에 서브클래스에서 슈퍼클래스에 있는 static 메서드와 같은 static 메서드를 정의하면 서로 별개의 메서드가 나온다. static의 특성상 정의된 클래스에 종속되기 때문에 같은 이름의 메서드라도 호출할 때 참조된 클래스에 맞추어 그에 속한 메서드가 실행된다.

     

    디폴트 인자 값에 대해서 Sub객체를 Super 타입 포인터 또는 참조로 접근하여 함수를 호출하면 함수의 바디는

    Sub것이 실행되지만 디폴트 인자는 Super에서 정의한 디폴트 인자값이 적용된다.

    이러한 이유는 C++에서 디폴트 인자값 결정할때 실제 이용된 객체는 안본다. 디폴트 인자값은 상속되지 않는다..

    디폴트 인자가 있는 함수를 오버로딩 할때는 항상 같은 값으로 디폴트 인자값을 설정해야한다. 또는 상수 심볼값으로 해놓던가..


    복제 생성자를 서브클래스에서 명시적으로 정의한다면..반드시 부모의 복제 생성자를 호출해야한다.

    안하면 부모의 디폴트 생성자가 호출된다(디폴트 복사생성자 아님) 그리고 대입연산자에 대해서도 항상 부모의 대입연산자를 호출해야한다.

    (부모는 상관없는데 자식에서 복제, 대입연산자를 구현한다면 부모에 대해서 호출해줘야하...)


    int* intP;

    int*& ptrRef = intP; //간단하게 보면된다. ptrRef는 int 타입 포인터 변수 inP에 대한 참조이다.

    참조형 변수를 멤버변수로 가질 수 있지만, 초기화는 이니셜라이저에서 해야한다. 생성자 바디에서는 안됨!

    참조형 변수를 상수값으로부터 초기화할 수 없듯이, 참조형 파라미터도 상숫값 인자를 받을 수는 없다.(const는 된다)

    C++11에서는 우측값 참조로 상숫값 인자를 참조형 파라미터 넘길 수 있다.


    전역변수는 초기화 순서가 특별히 보증되어 있지 않다..순서가 안정해져있음..그래서 서로 참조하면 위험함..또는 전역변수가

    다른 일반변수에 의존하면 문제다


    ---------------------------------------------------------------------------------------------------------------------------------------------------------


    C++11 내용
    ---------------------------------------------------------------------------------------------------------------------------------------------------------

    C++11 enum을 클래스로 작성이 가능하다고?

    enum class 클래스명 : unsingend long //이렇게 int타입말고도 다른타입하고도 연계된다.

    * 더이상 C+11에서는 enum과 정수 비교는 안된다.


    C++11 구간 지정 루프가 생겼다! auto도!

    구간 지정 for 루프

    int arr[]  = { 1,2,3,4};

    for(auto& i : arr)

    이런식으로 하면i에 arr값이 순차적..?


    C++11

    std::array

    vector와는 다르게 안전한 배열의 컨테이너이다. 범위를 벗어난 잘못된 메모리 작업의 위험이 없으며 오버헤드도 적다는 장점이 있다.

    단, 삭입 삭제는 못함.

    STL에서 사용할 수 있는 반복자와 알고리즘 모두 사용가능하다.



    C++11 스마트 포인터랑꼐

    int* ptr = new int[3];

    unique_ptr<int[]> ptr(new int[3]); 이런식으로 사용해야하며 스코프가 끝나면 알아서 해제된다.

    즉...스코프에 주의해야한다는것...!! 전역처럼은 못쓴다..지역이든 뭐든 스코프 ..

    shared_ptr은 레퍼런스 카운트로 해제..

    C++11에서는 더이상 auto_ptr이 사용되지 않음.

    unique_ptr은 내부에 복사 생성자와 할당 연산자가 아에 구현되어 있지 않다고라..이동만 가능하다고라...std::move    그래서 공유가 안된다. 치명적..

    shared_ptr은 공유가 가능하다..레퍼런스 카운트 활용..근데 이것도 위험하다.언제? 순환참조 상황이라면...커플링 관계지.. 으휴..

    그래서 해결한게 weak_ptr이다..그리고 shared_ptr은 카운트가 남았을떄 아무리 삭제하려고 해도 삭제가 안된다.

    하지만 weak_ptr은 shared_ptr의 객체만 참조할 뿐 레퍼런스 카운트를 올리지 않는다..

    사실 weak_ptr이 shared_ptr을 참조할때 shared_ptr의 레퍼런스 카운트를 증가하는데..생명 주기에 관여하는 strong 레퍼런스 카운트를 올리지 않을 뿐이다..(shared_ptr은 weak / strong 카운트로 나뉜다..)

    결국 weak_ptr은 shared_ptr의 참조자라는 표현이 더 맞을듯..

    shared_ptr<int> sp1(new int(5)) 라고 한다면

    weak_ptr<int> wp1 = sp1; 이렇게 ..그리고 포인터에 직접 참조가 안됨..

    weak_ptr은 다음과 같은 경우에 사용하면 유용하다.
    • 어떠한 객체를 참조하되, 객체의 수명에 영향을 주고 싶지 않은 경우
    • 그리고 매번 특정 객체의 ID로 컬렉션에서 검색하고 싶지 않을 경우
    • 그러면서 dangling pointer의 잠재 위험성을 없애고 싶을 때


    C++11

    명시적인 디폴트 생성자 표현방법

    구현부 없이도 디폴트 생성자(복사 생성자, 대입연산자도 동일하다)를 간편하게 선언할 수 있다.

    class CMyClass

    {

    public:

    CMyClass() = defalut;

    CmyClass(int i);

    };


    C++11

    Initializer_list

    initializer_list는 생성자의 인자로 가장 널리 사용된다. 아래의 코드에서 볼 수 있듯이 brace를 이용해서 객체를 초기화하는 경우에만 initializer_list를 파라메터로 취하는 생성자가 선택된다. 또한 이 생성자는 다른 생성자들보다 우선 순위가 더 높기때문에 적용이 가능한 상황이라면 더 부합하는 생성자가 있더라도 컴파일러는 이 생성자(initializer_list 파라메터를 가지는)를 우선적으로 선택한다. *심지어 복사생성자나 이동생성자보다도 우선적일 수있다.

    참고 사이트 : http://narss.tistory.com/entry/5%EC%9D%BC%EC%B0%A8-initializerlist

    C++11에서는 STL이 모두 이걸 지원한다. 그래서 vector<int> i = {1, 2, 4, 5}; 이런게 된다.

    이건 기존에 일일이 생성 후 push해주는거랑 같다 이건 유니폼 초기화하고도 연관이 있다.



    C++ 11

    클래스 선언부에서 바로 멤버변수를 = 으로 초기화 할 수 있다. 심지어 static 변수도 해당된다.


    C++11 

    생성자 위임이란 생성자 안에서 같은 클래스의 다른 생성자를 호출할 수 있다. 단

    이니셜 라이저에서 해야하며 A () : A(a) 이런식..그런데 두개의 생성자가 재귀적인 호출을 해서는 안된다.



    C++11

    이동생성자 는 연산 수행 후 우번의 객체가 성능 최적화 목적으로 소멸된다.


    C++11(우측값 참조)

    보통 C++에서는 좌항은 그 주소를 얻을 수 있거나 변수 이름을 가진 대상을 이야기한다. 우항은 좌항이 아닌것들 상숫값이나 임시 객체

    대입문의 오른쪽에 오는값에 대한 참조로 특히 우항이 임시 객체일 때 적용되는 개념이다.

    우측값 참조는 임시 객체가 생성되는 코드 구문에서 함수나 메서드 특히 복제 생성자나 대입연산자를 컴파일러가 선택할 떄 사용된다.

    이를 이용하면 삭제될 예정인 크기가 큰 임시 객체를 복제해야할때 전체 데이터를 깊게 복제하지 않고 포인터 주소만 얕게 복제하여

    오버헤드를 줄일 수 있다.

    선언할때 && 붙이면 된다. 보통 임시객체는 const type& 로 취급되지만 우측값 참조형 파라미터를 가지는 메서즈는 이 임시 개체 타입을 받아들일 수 있다.

    우측값 참조가 함수 파라미터에만 이용되는것이 아니다. 일반 변수도 우측값 참조형으로 선언해서 다른 타입들과 마찬가지로 값을 대입하는 등

    동일하게 사용할 수 있다. 하지만 일반 변수는 흔치 않은 경우다.


    기존C++은

    int& i = 2; //허용되지 않음 상수값에 대한 참조

    int a = 2, b = 3;

    int& j = a + b; //허용되지 않음 : 임시객체에 대한 참조


    하지만 우측값 참조를 이용하면 정상코드가 된다.

    int&& i = 2;

    int&& j = a + b;


    그런데 위에 코드처럼 우측값 참조를 독립적인 변수에서 이용하는 경우는 매우 드물다. 

    혹시 함수의 매개변수가 우측값 참조형 파라미터인데 인자값이 좌측값 참조형 파라미터(일반변수)라면 

    std::move()함수를 이용해 좌항변수를 우측값 참조로 변환할 수 있다. 그래서 우측값 파라미터의 함수가 호출된다.


    이동시맨틱(이동시맨틱, 이동생성자, 이동대입연산자)

    보통 우측참조와 비슷하게 대입 원본 객체가 임시 객체여서 대입 대상으로의 복제 또는 대입이 끝난 후 소멸할때 이들 메서드를 이용해 

    효율성을 도모한다. 

    이동생성자와 이동 대입연산자는 원본 객체로부터 새로운 객체로 멤버변수를 복사한 다음 원본 객체의 멤버를 null값으로 초기화시킨다.

    이렇게 함으로써 메모리에 대한 소윤권을 객체간에 이동시킨다. 메모리에 대한 소유권이전으로 댕글리나 릭발생을 방지한다.

    이동시멘틱은 우측참조연산자로 구현되고 이동 시멘틱을 갖으려면 생성자와 대입연산자를 구현해야한다.(그런데 얕은 복사로 해야한다..

    포인터는 그냥 주소값만 옮기고 원본은 nullptr로 해야함, 해제하면 큰일남..)

    그리고 대입연산자에서는 기존의 복사될 녀석의 메모리를 해제해야한다..잊지말고..

    A a(1,2);

    A b(2,3);

    a = b; //이때는 일반 대입연산자가 된다. 왜냐? 둘다 이름있는 일반 객체이니까..


    swap함수의 이동시멘틱으로 구현한다면

    template<typename T>
    void swap(T& a, T& b)

    {
        T temp(a);    //a를 로컬 변수 temp에 복제하고 파라미터 b를 a에 복제하고 다시 temp를 b에 복제하는 과정

    a = b;

    b = temp;

    }


    template<typename T>
    void swapMove(T& a, T& b)

    {
        T temp(std::move(a));

    a = std::move(b);

    b = std::move(temp);

    }



Designed by Tistory.