3. 자원 관리
• 자원, 가장 중요한 것은?
– “가져다 썼으면 해제해야 한다”
• 객체에 기반하여 자원을 관리하는 방법을
알아보자.
4. 스마트 포인터
• 스마트 포인터(auto_ptr)
– 소멸자가 자동으로 delete를 실행해 줌
• 예시
Character* pCh = createCharacter();
VS
std::auto_ptr<Character>pCh(createCharacter()
);
• 단, 복사가 자유롭지 못하다
– 다음장…
5. 스마트 포인터
• 단, 복사가 자유롭지 못하다
– 복사하는 객체만 해당 자원의 유일한 소유권을 가
질 수 있다.
– 원래 포인터는 NULL을 가리키게 된다.
• ex)
pCh1가 캐릭터를 가리키고 있다면
std::auto_ptr<Character>pCh2(pCh1);
// pCh1는 null이 됨. pCh2는 pCh1이 가리키던 객체
를 가리킴
pCh1 = pCh2; // 다시 pCh2는 null이 됨. pCh1은
pCh2가 가리키던 객체를 가리킴
6. 참조 카운팅 방식 스마트 포인터
• 몇 명이 참조하고 있는지 숫자를 셈
• 0명이 참조하고 있으면 자동으로
객체 삭제해주는 스마트 포인터
– 가비지 컬렉션과 비슷함.
• std::tr1::shared_ptr
• 사용방법 auto_ptr와 동일
• 단, 위 스마트 포인터들은 배열 객체에게는
사용하지 말 것! 배열 삭제 지원 안함.
7. 참조 카운팅 방식 스마트 포인터
• 몇 명이 참조하고 있는지 숫자를 셈
• 0명이 참조하고 있으면 자동으로
객체 삭제해주는 스마트 포인터
– 가비지 컬렉션과 비슷함.
• std::tr1::shared_ptr
• 사용방법 auto_ptr와 동일
• 단, 위 스마트 포인터들은 배열 객체에게는
사용하지 말 것! 배열 삭제 지원 안함.
8. RAII같은 객체 만들어보기
• 객체를 복사하면 어떻게 해야하나..?
– 어떤 동작이 이루어져야 하는지 혼돈의카오스
1. 복사를 금지한다.
– 복사 연산을 private 멤버로 만듬.
2. 관리하는 자원에 대해 shared_ptr 사용
3. 아예 깊은 복사를 해줘라
4. 소유권을 아예 넘겨줘라(auto_ptr처럼)
9. RAII같은 객체 만들어보기
• 객체를 복사하면 어떻게 해야하나..?
– 어떤 동작이 이루어져야 하는지 혼돈의카오스
1. 복사를 금지한다.
– 복사 연산을 private 멤버로 만듬.
2. 관리하는 자원에 대해 shared_ptr 사용
3. 아예 깊은 복사를 해줘라
4. 소유권을 아예 넘겨줘라(auto_ptr처럼)
10. RAII같은 객체 만들어보기
• 난 이렇게 호출하고 싶은데요?
– int hp = GetHP(pCh);
– 에러! pCh는 Character*가 아니라
tr1::shared_ptr<Character> 타입이라서…
• Get이라는 멤버 함수 하나 만들자!
– Character* GetHP() { return hp_; }
• 혹은 암시적 변환 함수를 사용하자
– Operator Character() const { return ch; }
Ex ) LevelUpCharacter(pCh, 22);
11. New, Delete
• delete와 delete[ ] 는 다르다!
– std::string *stringPtr1 = new std::string;
– std::string *stringPtr2 = new std::string[9];
– delete stringPtr1;
– delete [] stringPtr2;
• 대개 배열 원소의 개수가 힙 메모리에 들어
가기 때문!
• 그냥 vector 사용합시다…
12. 조심!
• New로 생성한 객체를 스마트 포인터에 넣는 코드
는 별도의 한 문장으로 합시다!
• processSomething(std::tr1:::shared_ptr<Character
>(new Character), createSomething());
이런거 하지 말자구요!
• 이유 : 컴파일러 제조사마다 param func의 호출 순
서가 다름.
디버깅 무지무지 힘들어짐.
• So,
std::tr1:::shared_ptr<Character> pCh(new Character);
processSomething( pch, createSomething() );
13. 인터페이스
• „제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵
게!‟
• Class … Date(int month, int day, int year)
– Date d(30, 3, 1995);
– Date d(3, 40, 1995);
– 둘 다 그래선 안되는데… 에러는 안남!
• Struct Day, Month, Year
– Date d(Month(3), Day(30), Year(1995));
• 사람이 실수 하지 않도록 도와주자.
14. 실수, 실수, 실수! 줄여보자!!
• Investment* createInvestment();
• 가능한 실수
– 포인터 삭제를 잊어버리거나
– Delete를 두 번 이상 해버리거나
• 스마트 포인터를 반환시켜보자
• std::tr1:shared_ptr<Investment> createInvestment();
• 삭제하는것을 깜빡해도 불상사 X
15. 자원 삭제하는 함수는 어떄?
• getRidOfInvestment 라는 함수
• 깔끔해 보이지만…
– 저 함수가 있다는 것을 까먹고 delete쓰면..?
• getRid…ment 와 tr1::shared_ptr를 묶자!
• std::tr1::shared_ptr<Investment>
pInv( static_cast<Investment*>(0), getRidOfInvestment );
• 저 함수를 createInvestment에서 실행!
16. tr1::shared_ptr의 장점
• 교차 DLL 문제
– 객체 생성시 DLL의 new를 사용했는데
다른 DLL의 delete를 쓸 경우
• tr1::shared_ptr은 new를 사용한 DLL의
delete를 사용하도록 구현되어 있음.
• DLL 꼬이는 문제 걱정 끝~!
17. 잊지 말자
• 좋은 인터페이스란?
– 제대로 쓰기엔 편하고
– 엉터리로 쓰기엔 어렵게!
• 사용자 실수를 방지해주자!
– 더이상 자원 관리 작업을 사용자 책임으로
내몰지 말자!
18. 클래스 설계를 타입 설계 하듯
• 좋은 클래스의 조건 3
1. 문법이 자연스럽고
2. 의미구조가 직관적이며
3. 효율적인 구현
19. 객체 설계,
하나 하나 따져가면서 신경써보자
1. 객체 생성 및 소멸은 어떻게 이뤄져야 하
는가?
2. 객체 초기화는 객체 대입과 어떻게 다른
가?
– 초기화랑 대입을 헷갈리지 말자!
3. 값에 의한 전달은 어떤 것을 의미하는가?
– 값에 의한 전달을 구현하는 쪽은 복사 생성자
20. 객체 설계,
하나 하나 따져가면서 신경써보자
4. 값들에 대한 제약을 무엇으로 둘 것인가?
– 데이터 멤버의 몇 가지 조합 값만은 반드시 유
효해야 함!
5. 기존 클래스 상속 계통에 맞출 것인가?
– 상속할 것인가, 멤버 함수가 가상인가 비가상
인가...
6. 어떤 종류의 타입 변환을 허용할 것인가?
– 필요하면 타입 변환 함수나 비명시호출 등을
구현해놓자.
21. 객체 설계,
하나 하나 따져가면서 신경써보자
7. 어떤 연산자, 함수를 두어야 할까?
8. 표준 함수들 중 어떤걸 허용하지 말까?
– 무엇을 private로 선언해야 할까?
9. 접근 권한을 어느 쪽에 줄 것인가?
– public, private, protected...
22. 객체 설계,
하나 하나 따져가면서 신경써보자
10. 비선언 인터페이스로는 뭘 둘 것인가?
11. 얼마나 일반적인가?
– 혹시 새로운 클래스를 정의하는게 아니라
새로운 클래스 템플릿을 정의해야 하는건 아닌
가?
12. 정말로 필요한가?
– 기존 클래스 기능 아쉬워서 파생 클래스 만들
고 있진 않은가? 그렇다면 그냥 비멤버 함수
를 만들자.
23. 상수객체 참조자에 의한 전달
• 값에 의한 전달
– 실제 인자의 „사본‟이 전달됨
– 은근 고비용의 연산
– 복사 생성자 한 번, 소멸자 한 번!
– 멤버 변수들의 생성자들도... 소멸자들도!!!
• 상수객체 참조자가 출동한다면!?
– void func(const Student& s);
– 생성자, 소멸자 전혀 호출 안됨.
– 복사에 의한 손실 문제도 없어짐.
24. 상수객체 참조자에 의한 전달
• 기본 제공 타입들은 값으로 전달해도 좋아
– 크기도 작고... 뭐 딱히 문제는 없잖아?
• 크기가 작은 객체들을 값으로 넘겨도..좋
아?
– 안됨.
– 생각보다 복사 생성자가 비쌀 수 있거든.
• 그럼 크기도 작고 복사 생성자도 작다면?
– 어떤 컴파일러에서는 기본 제공 타입은 레지스
터에 들어가지만 객체는 전혀 안들어가기도
함. 그냥 하지마 좀!!
25. 참조자를 반환하려고 하진 마!
const A& func(param)
• 객체 생성, 소멸에 드는 비용은 어찌하나?
– 그래? 참조자를 반환하면 비용 부담이 없지 않
을까??
• 그럼 그 참조할 객체는 누가 만들건데...
26. 참조자를 반환하려고 하진 마!
const A& func(param){
A res(param);
return res;
}
• 생성자 부르는거 싫어서 시작한 일인데?
– 아직도 생성자가 있음.
• res는 지역객체인데?
– 함수 끝나면 사라짐.
27. 참조자를 반환하려고 하진 마!
const A func(param){
A *res = new A(param);
return res;
}
• 생성자 부르는거 싫다니깐!
• 그리고 누가 delete 시켜줄건데?
28. 참조자를 반환하려고 하진 마!
const A func(param){
static A res;
res = ...
return res;
}
• 스레드 안전성 문제
• func(p1) == func(p2)는 항상 참이오. 헐...
29. 참조자를 반환하려고 하진 마!
그냥 새로운 객체를 반환시키자
inline const A func(param){
return A(param);
}
• 생성자... 쓰는 비용이 싫다매?
– 어쩔 수 없어. 최소한의 비용이야.
• 게다가 대부분 컴파일러에서 이런 부분은 최적화
되어있음
• 제대로 돌아가게 만드는데 신경 쓰자.
• 참조가 좋다고 해서
무조건 다 참조로만 하려곤 하지 말고!
30. DATA MEMBER
SHOULD BE in PRIVATE
• 왜 Public에다가 두면 안되죠?
– 문법적으로. 죄다 함수로 접근하게 하면 ()같은
거 써야하는지 안써야하는지 안헛갈려~
– 접근성에 대한 정교한 제어 가능
– 캡슐화.
– 로그찍기도 편하고
– 그 멤버변수를 변경하기가 매우 힘들어진다
• 그렇다고 protected는 된다는건 아니야!
– 똑같어... 잘 생각해바!
31. 비멤버 비프렌드 Func
• 상식적으로
– 데이터와 그 데이터 이용하는 함수는 붙어있어
야해!
– 정말... 그렇다고 생각하나요?
• 캡슐화를 한다는 것
– 바깥에서 볼 수 없다는 것
– 해당 기능을 바꿀 때 유연성이 증가
32. 비멤버 비프렌드 Func
• 멤버 함수들은
– 내부 private 함수들, 프렌드 함수들 등등 사용
가능해
• 비멤버 비프렌드 함수들은
– 당연히 내부 private 함수 전혀 불가능해!
– 어떻게 보면 더 캡슐화 된거 아니겠어?
• 그렇다고 다른 클래스의 멤버도 아닐 필요
는 없어!
– 그것은 딱히 캡슐화에 영향을 주지 않기 때문.
33. 비멤버 비프렌드 Func
• 그 비멤버 함수들을 어떻게 쉽게 관리하지?
– 해당 클래스와 같은 네임스페이스 안에 두는
것도 좋은 방법
• 컴파일 의존성이 고민된다면
– 네임 스페이스만 공유하고
– 서로 다른 .h 파일에서 작업해도 되지!
34. 매개변수, 타입 변환 해야하면
그 함수는 100% 비멤버
class A { .... operator * (const A& rhs) } }
...
A a, b;
A c = a * b; ---- OK
int i
A d = a * i; ---- OK
A e = i * a; ---- NO
35. 매개변수, 타입 변환 해야하면
그 함수는 100% 비멤버
int i;
A d = a * i; ---- OK
A e = i * a; ---- NO
• 왜 d는 되는 것일까?
– 컴파일러님은 알고계신대!
• A가 와야하는데 int형이 온 경우, 컴파일러가 알아채고
혹시 A의 생성자 중에 int형으로 가능한 게 있으면 그것
으로 암시적 타입 변환을 함.
– 물론 명시적 생성자를 만들면 에러~!
36. 매개변수, 타입 변환 해야하면
그 함수는 100% 비멤버
int i;
A d = a * i; ---- OK
A e = i * a; ---- NO
• 왜 d는 되는 것일까?
– 컴파일러님은 알고계신대!
• A가 와야하는데 int형이 온 경우, 컴파일러가 알아채고
혹시 A의 생성자 중에 int형으로 가능한 게 있으면 그것
으로 암시적 타입 변환을 함.
– 물론 명시적 생성자를 만들면 에러~!
37. 매개변수, 타입 변환 해야하면
그 함수는 100% 비멤버
class A { .... operator * (const A& rhs) } }
const A operator* ( const A& lhs, const A& rhs)
{..};
int i;
A d = a * i; ---- OK
A e = i * a; ---- OK
• 비멤버 함수로 만들어 놓으면 가능하지롱!
– lhs, rhs 둘다 암시적 타입 변환 수행하도록 냅둬!