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