2017년 7월 17일 월요일

C++ 주의사항

임시객체가 C++에서는 엄청난 중요도를 갖는다.

임시객체는 타입이름()로 생성한다. 임시객체는 기본은 문장 끝에서 사라진다. rvalue는 그냥 임시객체를 이야기 한다 생각하면 편하다. (물론 숫자 10같은 상수도 rvalue이다.)

함수 리턴이 참조가 아니라 값이면, 그건 언제나 임시객체를 넘기는 것이다.(최적화 되더라도 그렇게 이해 필요 함)

참조는 상수다(별칭). 선언시 반드시 참조할 대상을 지정해야 하며, 다른 대상으로 바꿀 수 없다.

이름이 있느냐에 따라  lvalue와 rvalue가 나뉜다. 가령 임시객체, 리터럴 등은 이름이 없으므로 rvalue다.
(단 const 변수는 변경 불가한 'lvalue' 임)

rvalue는 = 등호의 왼쪽에 올 수 없다.

임시객체는 참조 불가. const 참조 및 리턴값 예외.
복사 생성자는 파라미터로 const Type& a형으로 const 형을 받는데, 그건 임시객체로도 복사 생성이 가능하도록 하기 위한 것이다.

move 생성자는 임시객체처럼 곧 없어질 대상의 내용을 이동시킬 때 사용한다. 그런데 임시객체가 아니더라도 이동시키고자 하는 경우에는 그 객체를 임시객체의 참조 형태로 바꿔준다.

move 생성자는 Type&& a 형태를 받는다.(즉 rvalue reference) 때문에 복사 생성이 아닌 move 생성을 하고자 한다면, 복사할 객체가 rvalue 형태로 넘어와야 한다. 그것을 해주는 것이 move()로, move()는 복사할 객체가 a라면 내부에서는 static_cast<T&&>(a) 해준다.

void doSomthing(Item&& a)에서 &&는 rvalue reference 임.

그런데 탬플릿에서 &&가 쓰이면, 완전하게 다른 의미 임.
template <typename T> 
void doSomething(T&& a)에서 &&는 컴파일시 T에 따라 reference collapse가 일어나는 forwarding reference 임.

lvalue를 탬플릿의 forwarding reference로 넘길 때, 가령 move<T&&>(lv) 에서 타입<>를 생략하면, lvalue는 참조로 타입 추정된다.(즉 T는 lvalue의 실제 타입의 참조로 전달. 그리고 나서 reference collapse 규칙에 따라 & 와 &&이 합쳐져 &이 된다.)

reference collapse는 reference간 충돌이 일어날 때 줄이는 정책이며, &&와 &&이 만날때만 &&이 된다. 충돌이 없을 때는 적용되지 않음.

탬플릿에서 컴파일 시 결정되는 타입들은 꼭 그 바로 앞에 "typename" 키워드를 앞에 붙여주게 되어있음.

auto 우변의 수식으로 타입 추론
decltype() 괄호 안의 수식으로 타입 추론

&없는 auto는 무조건 값이다. 때문에 복사본 생성 및 복사가 일어난다. auto&를 해야 참조다.

auto는 타입을 가져오지만, 참조&나 const 같은 타입의 속성은 가져오지 않는다.

decltype은 원래 변수가 선언된 선언을 그대로 가져와서 교체된다.  다만, () 안에 내용이 단순 변수가 아니라 역참조 등이면, rvalue는 단순형으로, lvalue는 참조형으로 가져온다.

타입의 이름은 typeid(T).name()으로 출력 가능.

부분특수화. 탬플릿의 인자가 특정 값이나 타입 등일 때에 대해 정의할 수 있다.

new는 malloc과 다르다.
malloc은 메모리 할당만 하지만, 
new는 메모리 할당과 생성자 호출 2가지를 한다.

보통 C++에서 메모리만 할당할 때는 operator new를 쓰고
잡힌 메모리에 생성자를 부를 때는 
placement new를 쓴다. new(포인터) T(초기화 파라미터)

shared_ptr의 핵심은 
블럭을 벗어날 때 값 객체는 자동 파괴된다는 점. 값 객체(스마트포인터) 내에 진짜객체의 포인터를 가지고 있다가, 블럭을 벗어날 때 자동 파괴되면서, 진짜객체에 대한 참조개수 낮춤.

weak_ptr의 유일한 존재 이유는 shared_ptr의 상호참조 문제 해결.

객체 초기화 순서
부모생성자 ▷ 내멤버 ▷ 내생성자

서로 연관이 없는 타입에 대해
값 캐스팅은 모든 C++캐스팅으로 불가.
단 reinterpret_로 참조에 대해서는 캐스팅은 가능. 

변환 연산자 즉, operator 타입()을 이용해 좌변을 검사 가능하다.

대입 연산자 통해 우변을 검사 가능하다.

객체의 Exception safety는 
완전 강력 기본 3가지가 있다. 완전은 발생 가능성 없는 경우이고, 강력은 발생 가능하지만 회복하여 객체 재사용 가능. 기본은 회복 보장 안함. STL은 강력을 목표로 설계 됨. Read와 Remove API분리. 
스택 top과 pop 분리.

탬플릿은 type에 대해 실제 사용하는 경우가 없으면 코드화 되지 않는다.

탬플릿의 코드화(치환)은 컴파일 타임에 일어남.

코드화 실패는 에러가 아니다. Substitution Fail Is Not Error

컴파일 타임 타입 검사는 type_traits의 API활용.

enable_if, tag dispatching, constexpr if 등으로 컴파일 시점 탬플릿 치환 정의 가능함.

대표적인 경우가 클래스 복사 연산자에 대해 is_trivial이면 memcpy쓰고, 아니면 하나씩 복사해 주는 것을 탬플릿 인자에 따라 컴파일 시점에 결정 가능.

shared_ptr은 RefManager객체를 별도로 갖는다. RefManager 객체는 
strong : shared 개수
weak : shared + weak 개수
객체에 대한 포인터
destroyer 포인터
가짐. (16바이트 수준)

make_shared는 내부적으로 메모리를 할당하며, 객체와 RefManager의 크기를 한 덩어리로 잡는다. 때문에 객체는 RefManager 파괴조건(mWeak 0)까지 파괴되지 않는다.

함수 선언에 explicit을 써주면 암시적 형변환을 금지 한다. 단, if 문 안에서만 예외적으로 형변환 허용한다. safe bool

if 문 안에 올 수 있는 조건은
bool, 포인터, 실수 3가지이다.

forward declation 된 객체를 그 구현을 모르는 코드에서 delete 할때는 소멸자가 안불린다. 주의 필요.

댓글 없음:

댓글 쓰기