이 글의 모든 소스는 Android Framework 분석을 위한 C++ 강좌에 사용된 코드이며 저에겐 코드에 대해 아무런 권리도 없음을 밝힙니다.
1. 초기화 리스트(Colon Initialize)
클래스 멤버로로 상수나 참조 멤버가 있는 경우, 반드시 초기화리스트로 초기화해야 한다.
만약 디폴트 생성자가 없는 객체를 멤버로 가질 때 초기화 리스트로 초기화 해야 한다.
주의할 점은, 초기화는 클래스의 멤버가 선언된 순서대로 초기화 된다.
참고로 생성자를 클래스 외부에 구현했을 때 초기화 리스트는 다음 줄에 표기하자.
2. 상수 함수
상수 함수가 필요한 이유
우선 다음과 같은 코드를 보자.
근데 뭐 나는 const가 들어 갈 함수 안에 값을 안 바꿀거니, const 따위 필요 없다! 라고 생각하고 한번 제외하고 코드를 짜보자. 그러면 , print는 안에 아무것도 변하지 않으니 실행될 거 같지만?.. 안된다!
상수 객체는 상수 함수만 호출 할 수 있다.
그러면 뭐 난 상수랑 안 쓰겠다? 하면 될까? 다음 코드를 보자.
결론은 라이브러리 제작이나 완전한 클래스를 제공할려면 const를 만들어야 한다.
간단한 룰은 값을 변하게 하는 함수라면 const가 필요 없으나, 그 외에는 const를 붙여야 한다.
3. 복사 생성자
다음의 코드를 실행하면 결과가 어떻게 나올까?
결과는 다음과 같다.
start
생성자2
AAA
복사생성자 - p
foo
소멸자 - p
BBB
소멸자
여기서 다시 성능향상을 위해서 다음과 같이 수정하자.
다음 코드의 결과를 예측해 보자.
결과는 다음과 같다.
start
생성자1 - p1
AAA
생성자2 - p2
foo
복사생성자 - 임시객체
소멸자 - p2
소멸자 - 임시객체
BBB
소멸자
다시 성능 향상을 위해 임시객체에 대한 작업을 하려고 하지만, 예전과 달리 이번에는 전역객체가 아닌 지역객체를 리턴함으로, 다음과 같이 하면 안된다. (지역변수등을 리턴할 때, 참조를 쓰면 안된다.)
대신 RVO를 써서, 다음과 같이 바꾸면 된다.
한가지 주의할 점은, 만약에 우리가 복사생성자를 다음과 같이 만들었다고 치자(성능은 잠시 접어두고)
복사 생성자가 인자로 디폴트를 넘기는 이게 또 복사 생성자를 부르고 하는 무한 반복 코드가 되서 컴파일 에러가 난다.
복사 생성자는 또 디폴트 복사 생성자에 대한 문제점이 있는 해결책은 다음의 4가지이다.
(A) 깊은 복사 - 가장 널리 알려진 기술. 하지만 별로 좋지 않다.
(B) 참조 계수 - 가장 많이 사용하고, 가장 좋은 기술
(C) 소유권 이전 - 많이 사용하고 있진 않지만 swap등을 만들 때 좋다.
STL의 auto_ptr<>이 이 기술 사용
(D) 복사 금지
우선 기본적으로 얕은 복사 때문에 생기는 문제의 코드다.
깊은 복사 (성능의 문제가 있다)
참조계수(Referecen Counting)
소유권 이전 (살짝 헷갈리는 부분)
복사 금지
4. 싱글톤
오직 한개의 객체만을 만드는 디자인 패턴
우선 기본적인 코드를 보자.
이번엔 객체를 힙으로 만들자.
만약 우리가 Cursor말고도 KeyBoard라는 클래스를 똑같이 만들고 싶다면, 다음과 같이 코드 자동 생성을 작성해 보자.
아쉽게도 안드로이드에서는 템플릿을 응용한 기술 + 상속 기술을 사용한다. (템플릿이 조금 어렵게 구현된다) 이런한 기술을 mix in template이라고 한다.
5. This
기본적인 this의 사용법이다. 여기서 cout또한 this를 이용한 것을 볼 수 있다.
p1, p2처럼 2개의 Point를 생성하면, 각 멤버 data는 객체마다 존재하지만, 코드는 공유를 한다. 그럼 어떻게 멤버 함수가 불리울 때, 어느 객체인지 알 수 있을까?
위와 같이 컴파일 시에는 객체에 대한 인자를 넘길 수 있는 형태로 변환된다. 추가적으로 static의 경우 this가 필요치 않고, 사용할 수 없다.
6. 멤버 함수 포인터
7. 연산자 재정의
위의 코드에서 보면, 멤버 함수와 일반 함수의 용도에 따라 약간 다른 컴파일 결과가 나오는데,
일반 함수 vs 멤버 함수는 쉽게 생각해서, 연산의 결과로 멤버가 바뀔 거면(private이면) 일반함수에 접근이 불가능하기 때문에 멤버 함수로 하는게 좋고, 아닐 경우, 일반 함수로 하면 좋겠다.
증감연산도 보자.
우리가 만든 Int32도 당연히 ++가 잘 동작해야 한다. 하지만 오동작을 하는 것을 볼 수 있다( 5가 나오는 대신 4)
이를 수정하려면, 참조리턴을 해야 한다.
마찬가지로 후위형 또한 다음과 같이 할 경우가 문제 생기는데,
이를 수정하려면, 다음과 같이 한다.
한번 보면, (n2.operator++(int))는 Int32 n2가 상수가 아니기에 const Int32 operator++를 부르는데 문제가 없으나, 이를 통해 리턴되는 녀석은 상수(const)이다. 그럼 다시 (n2.operator++(int)).operator++(int)를 부르라고 하면, %상수 객체는 상수 함수만을 부를 수 있다%, 상수라서 부를 수 없다.
그리고 성능 향샹을 위해 전위형은 inline으로, 후의형은 다시 전위형을 호출하게 하자.
이걸로 보면 전위형이 후위형보다 성능이 좋다는 걸 알 수 있다.
for(int i = 0; i < 10; i++) 보다 for(int i = 0; i < 10; ++i)가 좋다는 말이다. 그러나 역시 똑똑한 우리의 컴파일러는 컴파일 시 이를 최적화 해 주므로 가독성을 위해 i++을 써도 된다.
8 스마트 포인터
스마트 포인터를 설명하기 위한 기본 코드를 보자.
변형을 조금 가해서 코드가 조금 어려워 진다.
sptr p를 보면.. sptr은 포인터가 아니고, sptr 타입인데, p->Go()를 보면 포인터같고 Car타입 같다.. >_<
sptr 안에는 Car* 포인터가 존재하고 sptr p = new Car 는 결구 sptr p(new Car)와 같다.
p->Go()의 경우 위에 정의된 operator->()에 따른다.
이것이 스마트 포인터라고 불리우는데, 이는 포인터가 아닌 객체(다른 객체의 포인터 역활을 한다.)이며, 스마트 포인터의 장점은 생성/복사/대입/소멸의 모든 과정을 사용자가 정의 할 수 있다는 것이다.
아래는 template을 써서 위의 sptr을 일반화한 코드이다.
스마트 포인터와 참조계수에 대해서 알아보자.
그러나 스마트포인터는 레퍼런스카운터에 대한 포인터 때문에 오버헤드가 있다. 그럼 레퍼런스를 객체에 두어보자.
마지막으로 안드로이드에서 쓰는 방식을 따라서 써 본 코드이다.
안드로이드는 객체가 만들어지면 레퍼런스 카운터가 1이 되고, 스마트 포인터로 가리키면 2가 된다.(보통 0에서 시작해서 스마트 포인터가 가리키면 1이된다).
9. 반복자
'프로그래밍 > C++' 카테고리의 다른 글
Android Framework 분석을 위한 C++ 4일차 (0) | 2012.04.26 |
---|---|
[C++] Smart Pointer (0) | 2012.04.25 |
Android Framework 분석을 위한 C++ 3일차 (0) | 2012.04.25 |
swap 함수 (0) | 2012.04.25 |
Android Framework 분석을 위한 C++ 1일차 (0) | 2012.04.23 |