SlideShare a Scribd company logo
1 of 132
Effective C++
C++에 왔으면 C++의 법을 따릅시다
Chapter
1
항목 1 : C++를 언어들의 연합체로 바라보는 안목은 필수
- C
- 객체 지향 개념의 c++
- 템플릿 c++
- STL
C++은 4가지의 하위 언어로 이루어져 있다.
c++의 어떤 부분을 사용하냐에 따라 효과적인 규칙이 달라진다
항목 2 :#define을 쓰려거든 const, enum, inline을 떠올리자.
선행처리자보다 컴파일러를 더 가까이 하자
#define ASPECT_RATIO 1.653;
컴파일러에게 넘어가기전에 선행처리자가 밀어버리고 숫자 상수로 바꾸어 버림
-> ASPECT_RATIO는 컴파일러가 쓰는 기호테이블에 들어가지 않는다.
-> 에러발생시에 햇갈리게된다.
해결법
: 매크로 대신 상수를 사용해라!
Const double AspectRatio = 1.653;
상수타입의 데이터이기 때문에 컴파일러도 알고 기호테이블에 들어간다.
항목 2 :#define을 쓰려거든 const, enum, inline을 떠올리자.
#define을 상수로 교체할때의 주의사항
1. 상수 포인터를 정의하는 경우.
포인터는 꼭 const로 선언해 주어야 하고, 이와 아울러 포인터가 가리키는 대상까지 const로 선언
Ex) const char * const authorName = “Scott Meyers”;
const std::string authorName(“Scott Meyers”); // char* 기반보다 string객체를 사용하는게 좋다.
2. 클래스 멤버로 상수를 정의하는 경우 : 클래스 상수를 정의하는 경우
어떤 상수의 유효범위를 클래스로 한정하고자 할 때는 그 상수를 멤버로 만들어야 하는데,
그 상수의 사본 개수가 한 개를 넘지 못하게 하고 싶으면 정적(static) 멤버로 만들어야 한다.
항목 2 :#define을 쓰려거든 const, enum, inline을 떠올리자.
Static은 헤더파일에서 선언하고 구현파일에서 정의한다.
나열자 둔갑술
항목 3 : 낌새만 보이면 const를 들이대 보자!
어떤 값이 불변이어야 한다는 제작자의 의도를 컴파일러 및 다른 프로그래머와 나눌 수 있는 수단
어떤 값을 변하게 하지 않게 하려면 const를 사용해야 한다.
Const키워드가 *표의 왼쪽에 있으면 포인터가 가리키는 대상이 상수
Const키워드가 *표의 오른쪽에 있으면 포인터 자체가 상수.
Const키워드가 *표의 양쪽에 있으면 가리키는 대상 및 포인터 둘 다 상수
항목 3 : 낌새만 보이면 const를 들이대 보자!
Const와 STL반복자
: 어떤 반복자를 const로 선언하는 일은 포인터를 상수로 선언하는 것
: 만약 변경이 불가능한 객체를 가리키는 반복자가 필요하다면 const_iterator을 사용
항목 3 : 낌새만 보이면 const를 들이대 보자!
멤버 함수에 붙는 const 키워드의 역할은 “해당 멤버 함수가 상수 객체에 대해 호출될 함수“
이런 함수가 중요한 이유
1. 클래스의 인터페이스를 이해하기 좋게 하기 위해서
- 그 클래스로 만들어진 객체를 변경할 수 있는 함수는 무엇이고,
- 변경할 수 없는 함수는 무엇인가를 사용자쪽이 알아야 하기 때문에
2. 이 키워드를 통해 상수 객체를 사용할 수 있게 하자는 것
항목 3 : 낌새만 보이면 const를 들이대 보자!
Const 키워드가 있고 없고의 차이만 있는 멤버 함수들은 오버로딩이 가능하다.
항목 3 : 낌새만 보이면 const를 들이대 보자!
비트 수준 상수성
어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 그 멤버 함수가 ‘const’임을 인정하는 개념
객체를 구성하는 비트들 중 어떤 것도 바꾸면 안된다는 것
항목 3 : 낌새만 보이면 const를 들이대 보자!
논리적 상수성
상수 멤버 함수라고 해서 객체의 한 비트도 수정할 수 없는 것이 아니라 일부 몇 비트 정도는 바꿀 수 있되, 그
것을 사용자측에서 알아채지 못하게만 하면 상수 멤버 자격이 있다.
항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자!
생성자에서는 객체의 모든 것을 초기화하자!
“대입을 초기화와 햇갈리지 않는것이 가장 중요하다“
왼쪽 값은 초기화가 아니라 대입이 되고, 오른쪽은 초기화가 되고있다.
왼쪽보다는 오른쪽이 훨씬 효율적이다.
항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자!
theName, theAddress 및 thePhones에 대해 기본 생성자를 호출해서 초기화를 미리
해 놓은 후에 생성자에서 곧바로 새로운 값을 대입하고 있다.
-> 먼저 호출된 기본 생성자에서 해 놓은 초기화는 헛짓이 됨.
항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자!
theName은 name으로 부터 복사생성자에 의해 초기화
theAddress는 address로 부터 복사생성자에 의해 초기화
thePhones도 phones로 부터 복사생성자에 의해 초기화
멤버 초기화 리스트
항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자!
클래스 객체의 생성자 초기화 순서
- 기본 클래스는 파생클래스보다 먼저 초기화
- 클래스 데이터 멤버는 그들이 선언된 순서대로 초기화
찾기 힘든 버그를 만들어낼 수 있기 때문에 멤버 초기화 리스트에 넣은
멤버들의 순서를 클래스에 선언한 순서와 동일하게 맞추는것을 권장
항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자!
정적객체
: 자신이 생성된 시점부터 프로그램이 끝날 때까지 살아 있는 개체를 일컫는다.
정적 객체의 범주에 들어가는 것들
1. 전역 객체
2. 네임스페이스 유효 범위에서 정의된 객체
3. 클래스 안에서 static으로 선언된 객체
4. 함수 안에서 static으로 선언된 객체
5. 파일 유효범위에서 static으로 정의된 객체
함수 안에 있는 정적 객체는 지역 정적 객체라고 하며,
나머지는 비지역 정적 객체라고 한다.
항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자!
별개의 번역 단위에서 정의된 비지역 정적 객체들의 초기화 순서는 정해져있지 않다.
싱글톤 패턴을..잘 모르겠음…
생성자_소멸자 및 대입 연산자
Chapter
2
항목 5 : C++이 은근 슬쩍 만들어 호출하는 함수들
직접 선언하지 않는다면 컴파일러는 다음 함수들을 선언해준다.
- 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자
(기본 생성자와 소멸자는 컴파일러에게 배후의 코드?의 자리를 마련한다)
특징
-기본 클래스의 소멸자가 비 가상 소멸자이면 파생클래스의 소멸자도 비 가상이다.
-상수/참조 타입 멤버변수가 있으면 대입 연산자는 직접 정의해 줘야 한다.
-기본 클래스에서 private으로 복사 대입연산자가 선언 되어있다면 파생 클래스에서 암시적으
로 만들어 주지 않는다.
항목 6 : 컴파일러가 만들어낸 함수가 필요 없으면 확실히 사용을 금하자
-실수로라도 복사생성자 및 복사 대입 연산자를 사용하고 싶지 않다면, private으로 선언해놓자.
(하지만 friend함수에서 불릴 여지가 있다.
이런 경우 정의를 해놓지 않는다면 링크에러가 나타나게 된다.)
만일 컴파일 시점에서 에러처리를 해놓고 싶다면
-복사 생성자 및 복사 대입 연산자를 private으로 선언 하는 복사 방지 기본클래스를 만들어 상
속받아 사용하자.
항목 7 : 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선
언하자!
-기본 클래스가 다형성을 가진 클래스일 때 비 가상함수를 사용한다면
기본 클래스의 포인터로 파생 클래스의 객체가 삭제될 때 파생 클래스가 소멸이 안 되는 문제
가 생긴다!!
즉, 다형성을 가진 기본 클래스에는 반드시 가상 소멸자를 선언해 주자!
(단 기본클래스로 설계된 클래스가 아니거나 다형성을 갖도록 설계된 클래스가 아니라면 남발
하지 말자.)
항목 8 : 예외가 소멸자를 떠나지 못하도록 붙들어 놓자
여러 소멸자에서 동시에 예외가 던져진다면 활성화된 예외가 축적 되 문제가 심각해 질것이다.
-std::abort() : 예외가 발생하면 프로그램을 강제 종료 시킨다.
-예외가 발생하면 로그찍고 진행한다.(프로그램이 올바르게 계속 실행될 보장이 있다면 해볼만
한 시도라고 한다.)
이는 둘다 적절한 해결방법이라고 보기 힘들다.
-즉 애초에 예외처리를 소멸자에서 하지 말고 다른 함수에서 비롯되어야 한다는 뜻!
(또한 사용자에게 함수를 호출 가능하게 하더라도 선택의 기회를 줌으로 부담을 덜어준다.)
항목 9 : 객체 생성 및 소멸 과정 중에는 절대로 가상함수를 호출하지 말자
-생성자에서 가상함수를 사용하게되면
파생 클래스의 객체를 생성할 때 먼저 불리는 기본 클래스의 생성자에서 가상함수가 호출되게
되면 아직 이 객체는 파생 클래스가 아닌 기본 클래스 타입이기 때문에 기본 클래스 타입의 가
상 함수가 호출 되게 된다!
-기본 클래스 생성자의 호출시: 자식 클래스 멤버는 아직 초기화 되지 않은 상태이다.
-기본 클래스 소멸자의 호출시: 자식 클래스의 멤버 변수는 이미 해제된 상태이다.
항목 9 : 객체 생성 및 소멸 과정 중에는 절대로 가상함수를 호출하지 말자
-이를 해결하기 위해 생성자에서는 비 가상함수를 사용하고 필요한 초기와 정보를 파생 클래스
에서 기본 클래스로 올려 보내준다.
항목 10 : C++의 대입 연산자는*this의 참조자를 반환하게 하자
클래스는 기본 타입과 되도록 똑같은 동작을 취하도록 설계하는 것이 좋다.
- Int x, y, z;
- x = y = z = 5;
와 같은 동작이 되도록 대입 연산은 참조자를 반환 하자
- T& operator=(const T& rhs)
{
……
return *this;
}
- +=, *= 등의 대입 연산자에도 동일하게 적용한다.
항목 11 : operator= 에서는 자기 대입에 대한 처리가 빠지지 않도록 하자
자기 대입이란 어떤 객체가 자기 자신에 대해 대입 연산자를 적용하는 것을 말한다.
-이 경우 제대로 처리해 놓지 않는다면 문제가 발생할 수 있다.
(중복 참조의 문제가 발생하는 것이다)
1.일치성 검사:
2.사본 복사 후 대입, 삭제:
원본을 기억하고 사본을 만든 뒤에 원본을 delete 해준다.
항목 12 : 객체의 모든 부분을 빠짐없이 복사하자
복사 생성자를 직접구현한 후에 추가로 데이터 멤버를 하나 추가하게되면
추가한 부분을 복사하지 못하는 현상이 발생한다.(컴파일러도 알려주지 않는다!)
- 해당 클래스의 데이터 멤버를 모두 복사했는지
- 이 클래스가 상속한 기본 클래스의 복사함수(복사 생성자/복사 대입연산자)도 잘 호출해 주
는지 주의해야 한다.
자원 관리
Chapter
3
항목 13 : 자원 관리에는 객체가 그만!
투자 객체(pInv)의 삭제가 실패할 수도 있다.
1. ‘…’ 어딘가에서 return문을 만나 도중 하차하는 경우
2. createInvestment 호출문과 delete문이 한 루프 안에서 continue 혹은 go to 를 만나게 되는 경우
3. 예외처리로 인해 delete를 거치지 않는 경우
자원을 객체에 넣고 그 자원 해제를 소멸자가 맡도록 하며, 그 소멸자는 실행 제어가 f를 떠날 때 호출 되도록 만드는 것
항목 13 : 자원 관리에는 객체가 그만!
SmartPoint(스마트 포인터)
New/delete와는 달리 가리키고 있는 대상에 대해 소멸자가 자동으로 delete를 불러주도록 설계 되어 있다.
항목 13 : 자원 관리에는 객체가 그만!
자원 관리에 객체를 사용하는 방법의 중요한 두 가지 특징
1. 자원을 획득한 후에 자원 관리 객체에게 넘긴다.
2. 자원 관리 객체는 자신의 소멸자를 사용해서 자원이 확실히 해제되도록 한다.
Auto_ptr은 자신이 소멸될 떄 자신이 가리키고 있는 대상에 대해 자동으로 delete를 하기 때문에
어떤 객체를 가리키는 auto_ptr의 개수가 둘 이상이면 절대로 안된다.
만약 둘 이상이 된다면 자원이 두번 삭제되는 결과를 낳는다.
Auto_ptr의 유별난 특성
- auto_ptr 객체를 복사하면 원본 객체는 null로 만든다.
항목 13 : 자원 관리에는 객체가 그만!
TR1에서 제공되는 tr1::shared_ptr
Auto_ptr을 쓸 수 없는 상황일아면 참조 카운팅 방식 스마트 포인터(RCSP)를 써라
: 특정한 어떤 자원을 가리키는 외부 객체의 개수를 유지하고 있다가 그 개수가 0이 되면 해당 자원
을 자동으로 삭제하는 스마트 포인터
항목 14 : 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자!
RAll 객체가 복사될 때 어떤 동작으로 이루어져야 할까?
항목 14 : 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자!
1. 복사를 금지한다.
-복사 연산을 private멤버로 만들어서 복사를 막을 순 있다.
2. 관리하고 있는 자원에 대해 참조 카운팅을 수행한다.
현재 tr1::shared_ptr방식으로 쓰이고 있다.
3. 관리하고 있는 자원을 깊은 복사로 수행
4. 관리하고 있는 자원의 소유권을 옮긴다.
항목 15 : 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자 !
daysHeld 함수는 Investment* 타입의 실제 포인터를 원하는데, tr1::shared_ptr<Investment> 타입의 객체를
넘기고 있어서 에러!
RAII 클래스의 객체를 그 객체가 감싸고 있는 실제 자원으로 변환할 방법이 필요하다.
1. 명시적 변환
2. 암시적 변환
항목 15 : 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자 !
1. 명시적 변환
Tr1::shared_ptr 및 auto_ptr은 명시적 변환을 수행하는 get이라는 멤버 함수를 제공
각 타입으로 만든 스마트 포인터 객체에 들어 있는 실제 포인터를 얻어 낼 수 있다.
항목 15 : 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자 !
2. 암시적 변환
Tr1::shared_ptr 및 auto_ptr은 포인터 역참조 연산자도 오버로딩 하고 있다.
항목 16 : new 및 delete를 사용할 때는 형태를 반드시 맞추자!
Std::string *stringArray = new std::string[100]
Delete stringArray // 에러
생성할때 배열로 생성했으면 해제할때도 배열로 해제해 주어야 한다.
Std::string *stringArray = new std::string[100]
Delete[] stringArray // 성공
단일 객체와 객체의 배열이 사용하는 힙의 구조가 다르기 때문에
객체의 배열은 그 위치에 배열의 개수를 갖고 있고, 단일 객체는 실제 그 object를 갖고 있음
항목 17 : new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으
로 만들자.
Int priority();
Void processWidget(std::Tr1::shared_ptr<Widget> pw, int priority);
Process widget 함수는 동적 할당된 widget 객체에 대해 스마트 포인터를 사용하도록 만들어짐
processWidget(new Widget, priority()); // 컴파일 에러
포인터를 받는 tr1::shared_ptr의 생성자는 explicit로 선언되어 있기 때문에, ‘new Widget’ 표현
식에 의해 만들어진 포인터가 tr1::shared_ptr 타입의 객체로 바꾸는 암시적인 변환이 있을 리가
없다.
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority()); // 컴파일 성공
항목 17 : new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으
로 만들자.
컴파일러는 processWidget 호출 코드를 만들기 전에 우선 이 함수의 매개변수로 넘겨 평가하는 순서를 밟는다.
- “new Widget” 표현식을 실행하는 부분
- Tr1::shared_ptr 생성자를 호출하는 부분
- Priority를 호출한다
- “new Widget”을 실행한다
- Tr1::shared_ptr 생성자를 호출한다.
- 각각의 연산이 실행되는 순서는 컴파일러마다 다르기 때문에 포인터 유실이 될 수 있다.
- Priority호출 부분에서 예외가 발생하게 되면 “new Widget”으로 만들어졌던 포인터가 유실
- Widget을 생성해서 스마트 포인터에 저장하는 코드를 별도의 문장 하나로 만들고, 그 스마트 포인터를
processWidget에 넘기는 것
Std::tr1::shraed_ptr<Widget> pw(new Widget);
processWidget(pw, priority()); // 컴파일 성공 -> 자원 누출 걱정 없음
설계 및 선언
Chapter
4
항목 18 : 인터페이스 설계는 제대로 쓰기 쉽게, 엉터리로 쓰기에 어렵게.
인터페이스 설계할 때 사용자의 입력 방향을 잡아줌으로 안정적인 설계가 가능하다.
- Struct Day{explicit Day(int day)…} //Month, Year 도 동일하다.
Data(const Month& m, const Day& d, const Year& y));
- Data d(Month(8), Day(4), Year(2017)); // 이와 같이 사용자가 헷갈리 여지를 최소화 해준다.
인터페이스를 설계할 때 일관성을 유지하는 것도 중요한 점이다.
- STL 컨테이너 에서 각 자료구조의 길이를 구하는 size 나 length로 설계 되어있는것이 좋은 예이다.
사용자의 실수를 막고 사전에 봉쇄할 수 있는 인터페이스를 설계 할 수 있도록 지속적으로 고민하자.
(tri::shared_ptr 에 대한 사용 이점을 설명해주고 있다. 이부분은 이해가 잘 가지 않아 좀더 공부가 필요하다.)
항목 19 : 클래스 설계는 타입 설계와 똑같이 취급하자.
클래스를 설계할 때, 새로운 타입을 설계하는 것과 같다. 12개의 질문을 던지고 있다.
1. 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하는가.
2. 객체 초기화에는 객체 대입과 어떻게 달라야 하는가?
-초기화와 대입을 헷갈리지 말아야 한다. 각각에 해당하는 함수 호출이 다르기 때문
3. 새로운 타입으로 만든 객체가 값에 의해 전달 되는 경우에 어떤 의미를 줄 것인가?
-값에 의한 전달은 복사 생성자에 의해 전달된다.
4.새로운 타입이 가질 수 있는 적법한 값에 대한 제약은 무엇으로 잡을 것인가?
-생성자, 대입 연산자 등 각종 쓰기 함수는 불변속성이 중요하기 때문
5.기존 클래스 상속 계통망에 맞출것인가?
-상속의 여부로 소멸자와 멤버 함수의 가상 비 가상함수를 맞춰 설계 해야한다.
항목 19 : 클래스 설계는 타입 설계와 똑같이 취급하자.
6.어떤 종류의 타임 변환을 허용할 것인가.
-암시적(implicit) 변환과 명시적(explicit) 타입 변환의 설계를 고려해야 한다.
7.어떤 연산자와 함수를 두어야 의미가 있을까?
-클래스에 선언할 함수이다. 어떤 것은 멤버함수로 어떤 것은 그렇지 않게 설계 될 것이다.
8.표준 함수들 중 어떤 것을 허용하지 말 것인가?
-복사 생성자/ 복사 대입연산자를 허용하고 싶지 않을 때 private으로 선언 하는 것과 같다.
9.새로운 타입의 멤버에 대한 접근 권한을 어느 쪽에 줄 것인가?
-private / protected/ public
10.선언 되지 않은 인터페이스 로 무엇을 둘 것인가?
-만들 타입이 제공할 보장이 어떤 종류일까에 대한 질문이다. 클래스 구현에 있어서 제약으로 작용하게 된다.
항목 19 : 클래스 설계는 타입 설계와 똑같이 취급하자.
11. 새로 만드는 타입이 얼마나 일반적인가?
-만일 타입 하나를 정의하는 것이 아닐지도 모른다. 이때에는 새로운 클래스 템플릿을 정의해야 할 것이다.
12.정말로 꼭 필요한 타입인가?
-기존 클래스에 대해 기능 몇 가지 추가를 위해 파생 클래스를 새로 만드는 것 보다 비 멤버 함수나 템플릿을 몇
개 더 정의하는 것이 효율적이다.
클래스를 설계하기 전에 위 사항들을 계속 점검해 보자.
항목 20 : ‘값에 의한 전달‘ 보다 ‘상수 객체 참조자에 의한 전달'을 하자.
C++은 기본적으로 값에 의한 전달이다.
-값에 의한 전달은 복사 생성자를 호출하여 전달 된다!
-뿐 만 아니라 소멸자도 호출 되게 된다.
이러한 비효율적 값 전달을 해결하기 위해 상수객체에 대한 참조자로 전달하게 설계한다.
- bool validateStudent(Student s); 를 bool validateStudent(const Student& s); 로 바꾼다.
복사 손실 문제를 해결 할 수 있다.
(파생 클래스의 객체가 기본클래스의 객체로 전달 될 때, 기본 클래스의 복사 생성자가 호출되어 파생 클래스의
객체로 동작하게 해주는 값들이 잘려나가는 현상)
항목 20 : ‘값에 의한 전달‘ 보다 ‘상수 객체 참조자에 의한 전달'을 하자.
항상 참조 전달이 좋은 것 만은 아니다.
- 전달 타입이 기본타입일 경우에는 값에 의한 전달이 효율적일 때가 많다.
-STL 반복자와 함수 객체도 마찬가지이다.(이때는 복사 효율을 높이고 복사 손실문제를 고려 해야한다)
크기가 작다고 값에 의한 전달이 효율적이지 않다.
-사용자 정의형 타입일 경우 언제 크기가 달라질지 모르고, 그 타입이 가르키는 포인터의 대상까지 복사하는 작
업이 일어날 수 있다.
항목 21 : 함수에서 객체를 반환해야 할 경우에 참조자를 반환하지 말자.
참조전달을 남용해서는 큰일이다.
스택에서 생성한 객체의 참조반환
-반환 형이 참조일 때 이미 소멸된 객체의 참조를 반환할 가능 성이 있다.
힘에서 동적 생성한 객체의 참조반환
-함수 내에서 delete를 해주는 것 또한 문제가 된다.
생성자 호출을 줄이고자 했지만 두 방법 다 어렵다.
즉, 가능하면 값으로 전달하자. (새로운 객체를 반환하게 만드는 방식)
항목 22 :데이터 멤버가 선언될 곳은 private 영역임을 명심하자.
Public 데이터 멤버는 왜 안 될까?
-문법적 일관성에 문제가 있다.
(사용자가 객체에 접근 가능한 방법이 모두 함수로 되어 있는 것이 일관성 있다)
-읽기, 쓰기 접근에 대한 제어가 힘들다.
(private으로 접근 방식을 유연하게 설계할 수 있다.)
-protected도 사실 public 과 큰 차이가 없다고 볼 수 있다.
(파생 클래스가 기본 클래스의 데이터 멤버에 높은 의존상태이다.)
즉, 요점은 캡슐화의 관점에서 쓸모 있는 접근 수준은 private이냐 아니냐 이다.
항목 23 :멤버 함수보다는 비 멤버, 비 프렌드 함수와 더 가까워 지자.
많은 캡슐화는 높은 유연성을 가지게 된다.
-어떤 데이터를 접근하는 함수가 많을 수록 그 데이터 캡슐화의 정도는 낮아지는 걸 의미한다.
왼쪽은 멤버 함수들을 호출하는 멤버함수 / 오른쪽은 객체를 받아 멤버 함수들을 호출하는 비 멤버함수 이다.
- 멤버 함수는 private데이터에 접근이 가능하지만 비 멤버(or 비 프렌드)함수는 private 데이터에 접근 할 수 없
다.
(또한 아래 비 멤버 함수 같은 경우는 편의성 함수라고 볼 수 있다. 이런 것은 namespace로 헤더파일에 선언 해
놓으면 보다 높은 확장성을 가지게 된다.)
항목 24 :타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비 멤버 함수를 선
언하자.
Rational 클래스에 대한 operator*를 모든 인자에 대해 암시적 타입 변환을 수행하고 싶다.
-우선 Rational 클래스 자체와 관련이 있으니 operator*는 멤버함수로 구현해보자.
const Rational operator* (const Rational& rhs) const; 로 선언.
-Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * oneEighth; //잘 된다.
하지만 우리는 혼합형 수치 연산도 하고싶다. 즉 Rational 을 int 같은 값과도 곱하고 싶다!
result = oneHalf *2; //잘 된다.
result = 2 * oneHalf; //에러!
항목 24 :타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비 멤버 함수를 선
언하자.
다음과 같은 형태로 파악해보자
result = oneHalf *2; //잘 된다.
result = 2 * oneHalf; //에러!
result = oneHalf .operator*(2); // oneHalf 객체는 operatior* 함수를 멤버로 갖고 있는 클래스의 인스턴스!
result = 2.operator*(oneHalf); // 하지만 2는 아니다.
이때 컴파일러는 비 멤버 버전의 operator* 를 찾아보지만 있을리가없다.
(Rational::operator* 선언문에서 lhs 부분에 this를 사용하기 때문이다.)
-rhs부분은 Rational temp(2);로 암시적 형 변환을 해준다. (명시적 형변환으로 선언 되있다면 이것도 에러!)
즉, 이때는 비 멤버 함수로 처리해야 한다.
const Rational operator* (const Rational& lhs, const Rational& rhs) const;
{
return Rational(lhs.numerator()*rhs.numerator(), lhs.denominator()*rhs.denominator()); //캡슐화된 접근
}
항목 25 :예외를 던지지 않는 swap에 대한 지원도 생각해보자
잘 동작하는 swap을 만들어야한다.
-표준 std::swap를 살펴 보자
Template<typename T>
void swap(T& a, T& b)
{
T temp(a);
a = b;
b = temp;
}
-T가 복사연산을 지원하면 제대로 동작한다.
-3번의 복사 연산 비용이 들어감이 예상된다.
항목 25 :예외를 던지지 않는 swap에 대한 지원도 생각해보자
- 표준에서 제공하는 swap이 우리 클래스 및 클래스 템플릿에 대해 납득할만한 효율을 보이면 그냥 사용하자.
하지만 그렇지 않다면 다음과 같은 방법이 있다.
- Swap함수를 해당 타입의 publi멤버 함수로 만든다.(단 예외가 던져지면 안 된다)
- 관련된 클래스 혹은 템플릿이 들어있는 namespace와 같은 장소에 비멤버 swap를 만들어 위에서 만든 public
swap함수에서 호출하게 한다.
- 새로운 클래스를 만들고 있다면 그클래스에 대한 std::swap의 특수화 버전을 만들고 이에 대해서도 swap 멤버
함수를 호출 하도록 하자.
- 후반부는 사실 거의 이해가 가지 않았다.. 더 공부하도록 하자
구현
Chapter
5
항목 26 : 변수 정의는 늦출 수 있는데까지 늦추는 근성을 발휘하자
If문에서 예외가 발생하면 encryped는 사용하지 않는 변수가 됨.
-> encrypted 객체의 생성과 소멸에 대해 비용을 내야 하므로 메모리 낭비
항목 26 : 변수 정의는 늦출 수 있는데까지 늦추는 근성을 발휘하자
예외 발생했을때의 낭비를 피하기 위해 늦게 선언을 했음.
하지만 초기화를 해주지 않아 효율적이지가 않다!
항목 26 : 변수 정의는 늦출 수 있는데까지 늦추는 근성을 발휘하자
1. 어떤 변수를 사용해야 할 때가 오기 전까지 그 변수의 정의를 늦춰야 함
2. 초기화 인자를 손에 넣기 전까지 정의를 늦출 수 있는지 둘러봐야함
-> 쓰지도 않을 객체가 만들어졌다 없어지는 일이 생기지 않으며,
불필요한 기본 생성자 호출이 일어나지 않고,
그 변수의 의미가 명확한 상황에서 초기화가 이루어지기 때문에,
변수의 쓰임새를 문서화하는데도 큰 도움이 된다.
항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자
C 스타일의 캐스트
(T) 표현식 // 표현식 부분을 T타입으로 캐스팅
함수 방식 캐스트
T(표현식) // 표현식 부분을 T타입으로 캐스팅
-> 이 두 방식은 구형 스타일의 캐스트
C++은 4가지의 새로운 캐스트 연산자를 독자적으로 제공
Const_cast<T>(표현식)
Dynamic_cast<T>(표현식)
Reinterpret_cast<T>(표현식)
Static_cast<T>(표현식)
항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자
Const_cast : 객체의 상수성을 없애는 용도로 사용
Dynamic_cast : ‘안전한 다운 캐스팅'을 할 때 사용하는 연산자
주어진 객체가 어떤 클래스 상속 계통에 속한 특정 타입인지 안인지를 결정
신경쓰일 정도로 런타임 비용이 높은 캐스트 연산자
Reinterpret_cast : 포인터를 int로 바꾸는 하부 수준 캐스팅을 위해 만들어진 연산자
Static_cast : 암시적 변환[비상수 객체를 상수 객체로 바꾸거나, int를 double로 바꾸는]을 강제로
진행할 때 사용
항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자
*this를 windo로 캐스팅
이에 따라 onResize는
Window::onResize가 됨.
함수 호출이 이루어지는 객체가 현재의 객체가 아님
*this의 기본 클래스 부분에 대한 사본이 임시적으로 만들어진다
onResize는 사본에서 호출된것 !
onResize를 수정하면 사본에서는 수정되지만,
현재 객체에선 수정되지 않는다.
항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자
현재 객체애 대고 onResize의 기본 클래스 버전을 호출하도록 만들면 현재 객체에서 수정이 됨
항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자
Dynamic_cast는 이러쿵 저러쿵 탈도 많은 연산자
깊이가 4인 단일 상속 계통에 속한 어떤 객체에 대해 이 연산자를 적용할 때,
Strcmp가 최대 4번까지 불릴 수 있다는 것.
상속 깊이가 더 깊거나 다중 상속에서 쓰이게 되면 비용이 더 커진다.
항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자
Dynamic_cast 연산자를 쓰고 싶어지는 때가 있긴 하다.
파생 클래스 객체임이 분명한 녀석이 있어서 이에 대해 파생 클래스의 함수를 호출하고 싶은데,
그 객체를 조작할 수 있는 수단으로 기본 클래스의 포인터밖에 없을 경우는 적지 않게 생김
1. 파생클래스 객체에 대한 포인터를 컨테이너에 담아둠으로써 각 객체를 기본 클래스 인터페이스를
통해 조작할 필요를 아예 없애 버리는 것.
2. 기본 클래스에 함수를 구현해서 가상 함수로 제공하는 것
항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자
1. 파생클래스 객체에 대한 포인터를 컨테이너에 담아둠으로써 각 객체를 기본 클래스 인터페이스를
통해 조작할 필요를 아예 없애 버리는 것.
항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자
2. 기본 클래스에 함수를 구현해서 가상 함수로 제공하는 것
항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자
정말 피해야 하는 설계
-폭포식 dynamic_cast
항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자
이것만은 잊지 말자
• 다른 방법이 가능하다면 캐스팅은 피해라
• 캐스팅이 어쩔 수 없이 필요하다면, 함수 안에 숨길 수 있도록 해라
• 구형 스타일의 캐스트를 쓰려거든 c++ 스타일의 캐스트를 선호하라.
항목 28 : 내부에서 사용하는 객체에 대한 ‘핸들'을 반환하는 코드는 되도록 피하자
항목 28 : 내부에서 사용하는 객체에 대한 ‘핸들'을 반환하는 코드는 되도록 피하자
클래스 데이터 멤버는 아무리 숨겨봤자 그 멤버의 참조자를 반환하는 함수들의 최대 접근도에 따라 캡슐화 정도가 정해진다.
“Ulhc와 lrhc는 privat로 선언되어 있지만, 이들의 참조자를 반환하는 upperLeft 와 lowerRight함수가 public”
이므로 실질적으로는 public 멤버이다.
사용자는 사각형을 정의하는 꼭짓점 쌍을 읽을 수는 있지만 쓸 수는 없게 된다.
항목 29 : 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자!
예외 안전성이 확보되는 코드란?
예외가 발생시에
문제점 1. 메모리 누수 - mutex unlock이 실행되지 못한다.
문제점 2. 기존 m_bgImage가 삭제된채로 throw된다.
항목 29 : 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자!
문제점 1. 메모리 누수 - mutex unlock이 실행되지 못한다.
해결 방법
자원 관리 클래스를 사용할 것
자원 관리 클래스의 소멸시 소멸자 호출로 뮤텍스 반환
항목 29 : 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자!
문제점 2. 기존 m_bgImage가 삭제된채로 throw된다.
해결 방법
1. 기본적인 보장 – 문제 없이 프로그램을 죽지 않게는 하지만
내부적으로 어떤 문제가 있을 지는 알 수 없음, 따로 예외처리 루틴을 만들어서 체크
2. 강력한 보장 – 마치 아무 일도 없었다는 듯이 정상 실행 -> 오류 발생시 이전상태로 실행
3. 예외 불가 보장 – 절대 예외가 발생하지 않음을 보장
항목 29 : 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자!
대체로 기본적인 보장 혹은 강력한 보장을 고른다.
강력한 보장을 지원하는 설계 방식
- 복사 후 맞바꾸기 기법
원본이 아닌 사본을 생성한 뒤, 사본을 수정
문제없이 사본이 완성되면, 원본과 바꿔치기
항목 30 : 인라인 함수는 미주알고주알 따져서 이해해두자!
함수처럼 보이고 함수처럼 동작하는데다가, 매크로보다 훨씬 안전하고 쓰기 좋은 인라인 함수
인라인 함수의 아이디어는 함수 호출문을 그 함수의 본문으로 바꿔치기 하자는 것!
인라인 함수의 장점 !
대체적으로 컴파일러 최적화는 함수 호출이 없는 코드가 연속적으로 이어지는 구간에 적용되도록 설계
인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별 최적화를 걸기가 용이해진다.
인라인 함수의 단점!
메모리가 제한된 컴퓨터에서 아무 생각 없이 인라인을 남발했다가는 프로그램 크기가 그 기계에서 쓸 수
있는 공간을 넘어 버릴 수 있다.
가상 메모리를 쓰는 환경일지라도 인라인 함수로 인해 부풀려진 코드는 성능의 걸림돌이 되기 쉽다
페이징 횟수가 늘어나고, 명령어 캐시 적중률이 떨어질 가능성도 높아진다.
항목 30 : 인라인 함수는 미주알고주알 따져서 이해해두자!
인라인 함수는 대체적으로 헤더 파일에 들어 있어야 하는게 맞다.
- 대부분의 빌드 환경에서 인라인을 컴파일 도중에 수행하기 때문에!
정말 간단한 함수라고 할 지라도 가상 함수 호출 같은 것은 절대로 인라인할수 없다!
Virtual의 의미가
“어떤 함수를 호출할지 결정하는 작업을 실행중에 한다"라는 뜻이고,
Inline의 의미가
“함수 호출 위치에 호출된 함수를 끼워넣는 작업을 프로그램 실행 전에 한다"라는 뜻이므로
가상함수호출에는 절대로 인라인을 해주지 않는다.
항목 30 : 인라인 함수는 미주알고주알 따져서 이해해두자!
결론 : 인라인 함수가 실제로 인라인 되느냐 안되느냐의 여부는 전적으로 개발자가 사용하는 빌드 환경에 달렸다.
인라인 함수로 선언된 함수를 함수 포인터를 통해 호출하는 경우도 대개 인라인되지 않는다.
항목 30 : 인라인 함수는 미주알고주알 따져서 이해해두자!
이것만은 잊지 말자
• 함수 인라인은 작고 자주 호출되는 함수에 대해서만 하는 것으로 묶어두자.
디버깅 및 라이브러리의 바이너리 업그레이드가 용이해지고, 자칫 생길 수 있는 코드
부풀림 현상이 최소화되며, 프로그램의 속력이 더 빨라질 수 있는 여지가 최고로 많아진다.
* 함수 템플릿이 대개 헤더 파일에 들어간다는 일반적인 부분만 생각해서 이들을 inline으로 선언하지 말자
항목 31 : 파일 사이의 컴파일 의존성을 최대로 줄이자.
이렇게 되면 birthdate와 theAddress가 바뀔 때마다 Person클래스가 컴파일이 됨 -> 퍼포먼스 낭비
= 분리 시켜야 낭비가 되지 않는다.(한쪽은 인터페이스만 제공하고, 한쪽은 그 인터페이스의 구현을 맡도록
항목 31 : 파일 사이의 컴파일 의존성을 최대로 줄이자.
Person의 사용자는 생일, 주소, 이름 등의 자질구레한 세부사항과는 완전히 갈라지게 된다.
인터페이스와 구현을 둘로 나누는 열쇠는 ‘정의부에 대한 의존성'을 ‘선언부에 대한 의존성’으로 바꾸어 놓는다.
컴파일 의존성을 최소화하는 핵심 원리 !
항목 31 : 파일 사이의 컴파일 의존성을 최대로 줄이자.
Person의 사용자는 생일, 주소, 이름 등의 자질구레한 세부사항과는 완전히 갈라지게 된다.
인터페이스와 구현을 둘로 나누는 열쇠는 ‘정의부에 대한 의존성'을 ‘선언부에 대한 의존성’으로 바꾸어 놓는다.
컴파일 의존성을 최소화하는 핵심 원리 !
상속 그리고 객체지향 설계
Chapter
6
항목 32 : public 상속 모형은 반드시 is-a를 따르자
DrivedClass is a BaseClass
기본 클래스는 파생 클래스보다 일반적인 버전이다.
- 새는 날수 있지만 팽귄은 날수 없다?!
(기본 클래스에서 Fly() 함수를 선언했다면 파생 클래스인 팽귄은 에러를 던지게 정의.)
(기본 클래스에서 Fly() 함수를 선언하지 말고 파생 클래스에서 날수 있는 새 클래스를 만든다.)
두번째 방법이 컴파일에서 에러가 나타나게 되 더 효율이 좋다.
기본 클래스에 적용되는 모든 것들이 파생 클래스에 그대로 적용되므로 신중한 설계가 필요하다.
항목 33 : 상속된 이름을 숨기는 일은 피하자
상속에서 이름 영역을 항상 고려하자.
기본 클래스에서 void mf3(); void mf3(double); 로 선언하고 파생 클래스에서 mf3(); 와 mf3(x); 를
모두 호출 했을 때, mf3(x); 는 찾지 못하고 에러가 나타난다.
(파생 클래스의 유효범위에서 대상을 찾았는데 인자가 다르기 때문이다.)
이를 해결하고 싶다면
- using선언 사용
( using Base::mf3(); ) private 상속에서는 불가능 하다.
-전달함수 사용
( virtual void mf3() { Base::mf3(); }; 와 같이 부모의 이름 영역에서 호출한다)
항목 34 : 인터페이스 상속과 구현 상속의 차이를 제대로 파악하고 구별하자
인터페이스 상속 : 멤버의 함수 선언만을 상속 받는 것.
구현상속 : 멤버 함수의 정의도 같이 상속 받는 것.
-순수 가상 함수: 파생 클래스에게 함수의 인터페이스만을 물려주는 것
(파생 클래스에서 반드시 구현 해야한다)
-비 순수(단순) 가상함수: 단순 가상함수를 선언하는 목적은 파생 클래스로 하여금 함수의 인터페
이스 뿐만 아니라 그 함수의 기본 구현도 물려받게 하자는 것이다.
(기본 구현체가 있는 것은 상속받는 새로운 객체가 재정의를 해야 함을 잊을 수 있는 위험이 있다.)
이는 비 순수 가상함수를 순수 가상함수로 만들고 비 가상함수로 default 함수를 만들어 사용한다.
virtual void fly(const Airport& destination){ defaultFly(destination);}
항목 35 : 가상함수 대신 쓸 것들도 생각해 두는 자세를 길러두자
Non-virtual Interface(NVI).
-public 비 가상 멤버 함수를 통해 private 가상 함수를 간접적으로 호출하게 만드는 기법.
-이때 쓰이는 비 가상 함수를 가상함수의 wrapper 라고 한다.
항목 35 : 가상함수 대신 쓸 것들도 생각해 두는 자세를 길러두자
Non-virtual Interface(NVI).
-사전/사후 동작이 파생 클래스에 모두 공통인 경우(자원 초기화등)
-인터페이스 함수는 언제 동작 할지 정한다.
-구현 가상 함수는 어떻게 구현할지 정한다.
함수 포인터로 구현한 전략 패턴
-NVI와 유사하다
-stategy패턴의 단순한 버전(예제 코드는 이해하기 어려웠다.. 공부하자)
(구현 함수 대신 외부 함수 포인터를 멤버 변수로 받아 사용/ 파생클래스에서 재정의하여 사용한
다.)
-실행 함수를 동적으로 변경 할 수 있다.
-하지만 클래스 멤버가 아니므로 private 접근이 불가능 하다.
항목 35 : 가상함수 대신 쓸 것들도 생각해 두는 자세를 길러두자
tr1::function으로 구현한 전략패턴(거의 이해가 가지 않음.)
-개념은 함수 포인터와 비슷하다.
-융통성 있는 활용이 가능하다.
고전적인 전략 패턴
-특정 기능을 수행하는 클래스를 외부에 만들고 원래 클래스가 그 클래스에 대한 객체를 가르키
는 포인터를 가지고 있고, 포인터는 생성자에서 초기화해 사용한다.
-전체적인 구조는NVI와 유사하다.
항목 36 : 상속받은 비가상 함수를 파생 클래스에 재정의하는 것은 절대 금물!
비 가상 함수는 정적 바인딩으로 묶인다.
-상속 받은 비가상 함수를 재정의하면 문제가 생긴다.
(기본 클래스 형 포인터로 파생 클래스를 가르킬 경우 기본 클래스의 비 가상함수가 호출 된다!)
즉, 상속 시킨 비 가상 함수는 불변의 동작을 구현한 것이다.
항목 37 : 어떤 함수에 대해서도 상속 받은 기본 매개변수 값은 절대로 재정의 하지
말자!
이 항목은 ‘기본 매개변수 값을 가진 가상 함수를 상속하는 경우'에 대한 이야기이다.
(기본 매개변수는 함수 선언에서 default 인자를 설정하는 것이다.)
(이때 기본 매개 변수 값은 정적 바인딩 된다.)
- 가상 함수의 호출은 동적으로 바인딩 된다.
- 비 가상 함수의 재정의 문제와 똑같은 문제가 발생한다.
기본 클래스 형 포인터로 자식 클래스의 객체를 가르키고 함수를 호출하면, 기본 클래스의 매개변
수를 이용해서 파생 클래스의 함수가 호출 되는 문제가 발생한다.
(NVI 방식을 통해 해결 할 수 있다.)
- 파생 클래스에서 재정의할 수 있는 가상함수를 private 멤버로 두고 이 가상 함수를 호출하는
publice 비가상 함수를 기본 클래스에 만들어 두는 것이다.
항목 38 : “has-a”혹은 “is-implemented-in-terms-of”를 모형화 할 때는 객체 합성을
이용하자.
객체 합성은 클래스를 멤버 변수로 갖고 있는 것이다.
has-a : ~는 ~를 가짐 (person has a address)
(사물을 본뜬 응용 영역)
Is-implemented-in-terms-of: ~는 ~로 구현됨
(시스템 구현을 위한 구현 영역)
다른 객체의 함수를 사용하여 구현에 적용한다.
std::list 로 set 구현의 예제가 있지만 완벽한 이해는 공부가 필요하다..
항목 39 : private 상속은 심사 숙고해서 구사하자.
Private 상속은 public 상속과 대조적으로 파생 클래스의 객체를 기본 클래스의 객체로 변환하지
않는다.
- 기본 클래스로 부터 물려받은 멤버는 파생 클래스에서 모조리 private 멤버가 된다.
- Is-implemented-in-terms-of 구현이라고 볼 수 있다. (구현만 물려받고 인터페이스는 안 됀다
라는 의미)
- 공백 클래스(정적 데이터 멤버가 없는 클래스)에 최적화 기법에 사용된다.
(공백 기본 클래스 최적화(EBO empty base optimization))
- 파생 클래스 쪽에서 기본 클래스의 protected멤버에 접근 해야할 경우 혹은 상속받은 가상함
수를 재정의 해야 할 경우에는 private상속이 나름대로 의미가 있다.
- 보통은 public 상속+객체합성 조합이 더 많이 사용된다.
항목 39 : 다중 상속은 심사 숙고해서 사용하자.
다중 상속은 모호성 문제가 가장 위험하다.
-둘 이상의 기본 클래스로부터 똑같은 이름의 함수 (또는 typedef 등)을 물려 받을때의 모호성.
다중 상속의 죽음의 마름모 꼴을 살펴보자
이때 File에 데이터멤버가 있다면 IOFile 에서는 데이터 멤버를
중복 생성할 우려가 있다.
-이때 File 클래스를 가상 기본 클래스로 만드는 것!
항목 39 : 다중 상속은 심사 숙고해서 사용하자.
가상 기본 클래스 상속
-비용이 비싸다.
-굳이 사용해야 한다면 가상 기본 클래스가 될 클래스에 데이터를 최대한 넣지 말자.
다만 다중 상속을 적법하게 사용할 경우가 있다.
- 인터페이스 클래스로 부터 public 상속을 시킴과 동시에 구현을 돕는 클래스로부터 private 상
속을 시키는 때이다.
(예제의 이해를 위해 더욱 공부하자)
템플릿과 일반화 프로그래밍
Chapter
7
항목 41 : 템플릿 프로그래밍의 천릿길도 암시적 인터페이스와 컴파일 타임 다형성
부터.
객체 지향 프로그래밍의 세계를 회전시키는 축은 명시적 인터페이스와 런타임 다형성
명시적 인터페이스
: 클래스에서는 그 인터페이스의 소스 코드를 보면 이것이 어떤 형태인지 확인할 수 있다.
암시적 인터페이스
: 템플릿은 그 템플릿이 구현된 모습을 보고 어떤 인터페이스가 필요한지를 유추할 수 있다.
템플릿화 될 T는 operator >, != size()등이 정의되어있어야 함을 알 수 있다.
컴파일 타임 다형성
: 템플릿을 인스턴스화 하는 과정에서 T의 타입이 결정된다 (어떤 매개변수가 들어가느냐에 따라
호출이 달라진다.
항목 42 : typename의 두 가지 의미를 제대로 파악하자
Class와 typename의 차이는 없다!
Typename을 쓰지 않으면 안되는 때가 있다.
의존이름 : 템플릿 내의 이름 중에 템플릿 매개변수에 종속된 것
중첩 의존 이름 : 의존 이름이 어떤 클래스 안에 중첩되어 있는 경우
비의존 이름 : 템플릿 매개변수가 어떻든 상관없는 타임
항목 42 : typename의 두 가지 의미를 제대로 파악하자
의존 이름은 동일한 클래스(C) 가 있다면 정상동작 하지 않을 수가 있다.
(C::const_iterator가 진짜 타입인지 아닌지 알아낼 방법은 없다!!)
(const_iterator라는 이름을 가진 정적 데이터 멤버가 C에 들어있다고 볼 수도 있다.
따라서 앞에 typename을 붙여주어 타입이라고 지정해주어야 한다.
항목 43 : 템플릿으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자
완전 템플릿 특수화
템플릿이 클래스 A를 상속받은 클래스 B가 A의 함수를 사용한다면 컴파일러가 이 클래스 A가 어디서
파생된 것인지를 찾을 수 없어 에러가 발생한다.
Template <typename T>
Class A
{
Public:
Void SendClear();
}
Class B : public A<T>
{
void func()
{
SendClear();
}
}
항목 43 : 템플릿으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자
기본 클래스에 접근할 때는 아래의 세 가지 방법을 사용한다.
This->SendClear();
항목 43 : 템플릿으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자
기본 클래스에 접근할 때는 아래의 세 가지 방법을 사용한다.
Using A<T>::SendClear;
항목 43 : 템플릿으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자
기본 클래스에 접근할 때는 아래의 세 가지 방법을 사용한다.
A<T>::SendClear();
이 방법은 추천하지 않는다.
호출되는 함수가 가상 함수인 경우에는, 명시적 한정을 해 버리면 가상 함수 바인딩이 무시되기 때문
항목 44 : 매개변수에 독립적인 코드는 템플릿으로.
행과 열의 크기를 나타내는 상수만 빼면 두 함수는 완전히 똑같아!!
템플릿을 포함한 프로그램이 코드 비대화를 일으키는 일반적인 형태 중 하나
항목 44 : 매개변수에 독립적인 코드는 템플릿으로
매개변수(template<T>에서 T)에
독립적인 코드는 템플릿에서 분리
Base template class를 만들어
독립적인 함수를 넣고 이를 상속받아
자식클래스에서 매개변수를
사용하는 부분 작성
항목 45 : “호환되는 모든 타입”을 받아들이는 데는 멤버 함수 템플릿이 직방
멤버 함수 템플릿은 어떤 클래스의 멤버 함수를 찍어내는 템플릿!
일반 포인터는 부모 클래스 포인터로 자식 클래스를 가르 킬 수 있다. 하지만 shared_ptr<>같은 템플릿
에서는 지원하지 않는다. 이를 해결하기 위해 멤버 함수 템플릿을 사용한다.
생성자를 무제한 만들기 어렵기 때문에, 생성자를 만들어내는 템플릿
모든 T타입 및 모든 U타입에 대해서 SmartPtr<T> 객체가 SmartPtr<U>로 부터 생성될 수 있다.
SmartPtr<U>의 참조자를 매개변수로 받아들이는 생성자가 SmartPtr<T> 안에 들어있기 때문
일반화 복사 생성자 라고 한다.
항목 45 : “호환되는 모든 타입”을 받아들이는 데는 멤버 함수 템플릿이 직방
SmartPtr<T>의 일반화 복사 생성자는 호환되는 타입의 매개변수를 넘겨받을 때만 컴파일된다.
항목 45 : “호환되는 모든 타입”을 받아들이는 데는 멤버 함수 템플릿이 직방
멤버 함수 템플릿은 코드 재사용만큼이나 기특하고 훌륭한 기능이지만, c++언어의 기본 규칙까지는 바꾸지 않는다.
이것만은 잊지 말자!
호환되는 모든 타입을 받아 들이는 멤버 함수를 만들려면 멤버 함수 템플릿을 사용하자.
일반화된 복사 생성 연산과 일반화된 대입 연산을 위해 멤버 템플릿을 선언했다 하더라도,
보통의 복사 생성자와 복사 대입 연산자는 여전히 직접 선언해야 한다.
항목 46 : 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의
항목 46 : 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의
Operator* 함수를 호출 할 때 어떤 함수를 호출하려는지 알 수 없다.
Operator*의 템플릿 T가 무엇인지 모르기 때문에..
컴파일러는 템플릿 인자 추론을 통해 인자를 찾아
oneHalf의 T가 int형인지는 알 수 있지만,
2의 경우는 처리를 할 수 없다.
템플릿 인자 추론 과정에서 암시적 변환을 고려하지 않기 때문이다
항목 46 : 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의
클래스 템플릿 안에 프렌드 함수를 넣어 두면 함수 템플릿으로서의 성격을 주지 않고 특정한 함수 하
나를 나타낼 수 있다는 사실
항목 46 : 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의
클래스 템플릿 안에 프렌드 함수를 넣어 두면 함수 템플릿으로서의 성격을 주지 않고 특정한 함수 하
나를 나타낼 수 있다는 사실
정의가 없기 때문에 링커가 찾지를 못한다.
항목 46 : 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의
Operator* 함수의 본문을 선언부와 붙여서 해결!
항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
STL은 기본적으로 컨테이너 및 반복자, 알고리즘의 템플릿으로 구성되어 있지만,
이 외에 유틸리티라고 불리는 템플릿도 몇 개 들어있다.
이들 중 하나가 advance라는 이름의 템플릿인데, 지정된 반복자를 지정된 거리만큼 이동시키는 것
+= 연산을 지원하는 반복자는 임의 접근 반복자 밖에 없기 때문에, 구현이 안된다.
항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
STL Iterator – Input iterator
입력 반복자
전진만 가능하고, 한번에 한칸씩만 이동하며, 자신이 가리키는 위치에서 읽기만 가능하다.
읽을 수 있는 횟수도 한번
입력 파일에 대한 읽기 전용 파일 포인터를 본떠서 만들었다.
STL반복자는 각 반복자가 지원하는 연산에 따라 5개의 범주로 나뉜다.
항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
STL Iterator – output iterator
출력 반복자
입력 반복자와 비슷하지만 출력용
앞으로만 가고, 한 번에 한칸씩만 이동하며, 자신이 가리키는 위치에서 쓰기만 가능
쓸 수 있는 횟수가 한번
출력 반복자는 출력 파일에 대한 쓰기 전용 파일 포인터를 본떠서 만들었다.
항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
STL Iterator – forward iterator
순방향 반복자
입력 반복자와 출력 반복자가 하는 일은 기본적으로 다 할 수 있다.
자신이 가리키고 있는 위치에서 읽기와 쓰기를 동시에 할 수 있다.
이 반복자는 multi-pass 알고리즘에 사용할 수 있다.
항목 46 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
STL Iterator – bidirectional iterator
양반향 반복자
순방향 반복자에 뒤로 갈 수 있는 기능을 추가한 것이다.
STL list에 쓰는 반복자가 이 범주에 속한다.
항목 46 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
STL Iterator – random access iterator
임의 접근 반복자
양방향 반복자에"반복자 산술 연산" 수행 기능을 추가하였다.
반복자의 임의의 거리만큼 앞뒤로 이동시키는 일을 상수 시간 안에 할 수 있다.
Vector, deque, string에 사용하는 반복자는 임의 접근 반복자이다.
항목 46 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
C++ 표준 라이브러리에는 지금까지 말씀드린 다섯 개의 반복자 범주 각각을 식별하는
데 쓰이는 “태그(tag) 구조체"가 정의되어 있다.
항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
반복자들이 종류마다 가능한 것이 있고 불가능한 것이 잇다는 점을 안이상 구현할때 신경을 써야 함
최소 공통 분모 전략 : 반복자를 주어진 횟수만큼 반복적으로 증가시키거나 감소시키는 루프를 돌리는 것
-> 선형 시간이 걸린다는 것은 쉽게 예측할 수 있다.
상수 시간의 반복자 산술 연산을 쓸 수 있는 임의 접근 반복자 입장에서는 손해
Iter부분이 임의 접근 반복자인지를
판단할 수 있어야 한다.
-> 어떤 타입에 대한 정보를 얻어내야 함
-> 특성정보를 사용한다
항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
특성 정보(traits) : 컴파일 도중에 어떤 주어진 타입의 정보를 얻을 수 있게 하는 객체를 지칭하는 개념
Deque의 반복자는 임의 접근 반복자 이므로 다음과 같은 형태
항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
특성 정보(traits) : 컴파일 도중에 어떤 주어진 타입의 정보를 얻을 수 있게 하는 객체를 지칭하는 개념
list의 반복자는 양방향반복자 이므로 다음과 같은 형태
항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
포인터 타입의 반복자를 지원하기 위해 iterator_traits는 포인터 타입에 대한
부분 템플릿 특수화 버전을 제공하고 있다.
다른 사람이 사용하도록 열어 주고 싶은 타입 관련 정보를 확인한다
그 정보를 식별하기 위한 이름을 선택한다
지원하고자 하는 타입 관련 정보를 담은 템플릿 및 그 템플릿의 특수화 버전을 제공한다.
항목 48 : 템플릿 메타 프로그래밍, 하지 않겠는가?
템플릿 메타프로그래밍(TMP)은 컴파일 도중에 실행되는 템플릿 기반의 프로그램을 작성하는 일
TMP의 강점
1. TMP를 쓰면 다른 방법으로는 까다롭거나 불가능한 일을 굉장히 쉽게 할 수 있다.
2. 템플릿 메타프로그램은 c++ 컴파일이 진행되는 동안에 실행되기 때문에, 기존 작업을 런타임 영역에서 컴파일 타
임 영역으로 전환할 수 있다.
TMP에는 반복의미의 진정한 루프는 없기 때문에, 재귀를 사용해서 루프의 효과를 나타낸다.
TMP의 루프는 재귀 함수 호출을 만들지 않고 재귀식 템플릿 인스턴스화를 한다.
New 와 delete를 내 맘대로
Chapter
8
항목 49 : new 처리자의 동작 원리를 제대로 이해하자.
operator new 함수가 메모리 할당 요청을 맞추지 못할 때 예외를 던지게 되어 있다.
이 예외를 던지기 전에 사용자 쪽에서 지정할 수 잇는 에러 처리 함수를 우선적으로 호출하도록
되어 있는데 이 함수가 new처리자(new-handler)라고 한다.
set_new_handler가 받아들이는 new_handler타입의 매개변수는 요구된 메모리를 operator new
가 할당하지 못했을 때 operator new가 호출할 함수의 포인터 이다.
항목 49 : new 처리자의 동작 원리를 제대로 이해하자.
new처리자의 용도
1.사용 할 수 있는 메모리를 더 많이 확보한다.
(프로그램 시작 시 따로 메모리를 잡아놓고 new 처리자 호출시 사용하는 방법)
2.다른 new 처리자를 설치
(현재의 new 처리자가 메모리 할당을 해주지 못할 때 다른 new 처리자를 호출해 해결한다.)
3.new 처리자의 설체를 제거
(set_new_handler 에 널 포인터를 넘긴다)
4.예외를 던진다.
(bad-alloc 또는 이에 파생된 타입의 예외를 던진다.)
5.복귀하지 않는다.
(abort 혹은 exit 를 호출한다.)
항목 50 : new와 delete를 언제 바꿔야 좋은 소리를 들을지 파악해 두자.
왜 new와 delete를 오버라이딩 해야 하는가?
1.잘못된 힙 사용을 탐지 하기 위해
(버퍼오버 플로우 같이 메모리 할당 영역의 침범을 막는 등)
2.효율 향상을 위해
(범용성을 위한 기본 new/delete는 사용자 정의용보다 효율이 떨어질 수 있다.)
3.동적 할당 메모리의 실제 사용에 관한 통계 정보를 수집하기 위해
(할당된 메모리의 블록크기/ 사용기간 분포/ 메모리 사용 패턴 등)
4.할당 및 해제 속력을 높이기 위해
(역시 사용자 정의 버전이 특정 타입의 객체에 맞춰 설계되어 있으면 기본 제공 타입보다 빠를 것
이다.)
5.기본 메모리 관리자의 공간 오버헤드를 줄이기 위해
항목 50 : new와 delete를 언제 바꿔야 좋은 소리를 들을지 파악해 두자.
왜 new와 delete를 오버라이딩 해야 하는가?
6.적당히 타협한 기본 할당자의 바이트 정렬 동작을 보장하기 위해.
(컴파일러에 따른 바이트 정렬을 사용자 정의 버전으로 바꿈으로 수행성능을 올릴 수 있다.)
7.임의의 관계를 맺고 잇는 객체들을 한 군데 나란히 모아 놓기 위해
8.그때 그때 원하는 동작을 수행하도록 하기 위해
요점은 개발자 스스로 사용자 정의 new/delete를 작성하는데 여러 타당한 이유가 있다.
항목 51 : new및 delete를 작성 할 때 따라야 할 기존의 관례를 잘 알아 두자
-관례적으로 operator new 함수는 메모리 할당을 반복해서 시도하는 무한 루프를 가져야 하고,
메모리 할당 요구를 만족시킬 수 없을 때 new 처리자를 호출해야 하며, 0바이트에 대한 대책도
있어야 한다.
클래스 전용 버전은 자신이 할당하기로 예정된 크기보다 더 큰 메모리 블럭에 대한 요구도 처리
해야 한다.
-operator delete 함수는 널 포인터가 들어왔을 때 아무 일도 하지 않아야 합니다. 클래스 전용 버
전의 경우에는 예정 크기보다 더 큰 블록을 처리해야 한다.
항목 52 : 위치지정 new를 작성한다면 위치지정delete 도 같이 준비하자.
-operator new 함수의 위치지정 버전을 만들 땐, 이 함수와 짝을 이루는 위치지정 버전으
operator delete 함수도 꼭 만들어 주자. 메모리 누출 현상을 겪을 수 있다.
-new 및 delete의 위치지정 버전을 선언할 때는, 의도한 바도 아닌데 이들의 표준 버전이 가려지
는 일이 생기지 않도록 주의해야 한다.

More Related Content

What's hot

이펙티브 C++ 공부
이펙티브 C++ 공부이펙티브 C++ 공부
이펙티브 C++ 공부quxn6
 
Effective c++ 챕터 2 정리
Effective c++ 챕터 2 정리Effective c++ 챕터 2 정리
Effective c++ 챕터 2 정리연우 김
 
Effective c++ 정리 chapter 6
Effective c++ 정리 chapter 6Effective c++ 정리 chapter 6
Effective c++ 정리 chapter 6연우 김
 
Effective c++ 1,2
Effective c++ 1,2Effective c++ 1,2
Effective c++ 1,2세빈 정
 
More effective c++ chapter1,2
More effective c++ chapter1,2More effective c++ chapter1,2
More effective c++ chapter1,2문익 장
 
Effective c++ chapter 1,2 요약
Effective c++ chapter 1,2 요약Effective c++ chapter 1,2 요약
Effective c++ chapter 1,2 요약Nam Hyeonuk
 
NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++
NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++
NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++Min-soo Park
 
이펙티브 C++ 스터디
이펙티브 C++ 스터디이펙티브 C++ 스터디
이펙티브 C++ 스터디quxn6
 
Effective c++ 정리 chapter 4
Effective c++ 정리 chapter 4Effective c++ 정리 chapter 4
Effective c++ 정리 chapter 4연우 김
 
이펙티브 C++ 5,6 장 스터디
이펙티브 C++ 5,6 장 스터디이펙티브 C++ 5,6 장 스터디
이펙티브 C++ 5,6 장 스터디quxn6
 
Effective c++ 4
Effective c++ 4Effective c++ 4
Effective c++ 4현찬 양
 
이펙티브 C++ (7~9)
이펙티브 C++ (7~9)이펙티브 C++ (7~9)
이펙티브 C++ (7~9)익성 조
 
Effective java
Effective javaEffective java
Effective javaHaeil Yi
 
Effective c++ chapter3, 4 요약본
Effective c++ chapter3, 4 요약본Effective c++ chapter3, 4 요약본
Effective c++ chapter3, 4 요약본Dong Chan Shin
 
More effective c++ chapter1 2_dcshin
More effective c++ chapter1 2_dcshinMore effective c++ chapter1 2_dcshin
More effective c++ chapter1 2_dcshinDong Chan Shin
 
More effective c++ 챕터3~4ppt
More effective c++ 챕터3~4pptMore effective c++ 챕터3~4ppt
More effective c++ 챕터3~4pptInjae Lee
 
Effective C++ 정리 chapter 3
Effective C++ 정리 chapter 3Effective C++ 정리 chapter 3
Effective C++ 정리 chapter 3연우 김
 
More effective c++ 항목30부터
More effective c++ 항목30부터More effective c++ 항목30부터
More effective c++ 항목30부터Dong Chan Shin
 
Effective c++ 2
Effective c++ 2Effective c++ 2
Effective c++ 2현찬 양
 

What's hot (20)

이펙티브 C++ 공부
이펙티브 C++ 공부이펙티브 C++ 공부
이펙티브 C++ 공부
 
Effective c++ 챕터 2 정리
Effective c++ 챕터 2 정리Effective c++ 챕터 2 정리
Effective c++ 챕터 2 정리
 
Effective c++ 정리 chapter 6
Effective c++ 정리 chapter 6Effective c++ 정리 chapter 6
Effective c++ 정리 chapter 6
 
Effective c++ 1,2
Effective c++ 1,2Effective c++ 1,2
Effective c++ 1,2
 
More effective c++ chapter1,2
More effective c++ chapter1,2More effective c++ chapter1,2
More effective c++ chapter1,2
 
Effective c++ chapter 1,2 요약
Effective c++ chapter 1,2 요약Effective c++ chapter 1,2 요약
Effective c++ chapter 1,2 요약
 
NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++
NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++
NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++
 
이펙티브 C++ 스터디
이펙티브 C++ 스터디이펙티브 C++ 스터디
이펙티브 C++ 스터디
 
Effective c++ 정리 chapter 4
Effective c++ 정리 chapter 4Effective c++ 정리 chapter 4
Effective c++ 정리 chapter 4
 
이펙티브 C++ 5,6 장 스터디
이펙티브 C++ 5,6 장 스터디이펙티브 C++ 5,6 장 스터디
이펙티브 C++ 5,6 장 스터디
 
Effective c++ 4
Effective c++ 4Effective c++ 4
Effective c++ 4
 
이펙티브 C++ (7~9)
이펙티브 C++ (7~9)이펙티브 C++ (7~9)
이펙티브 C++ (7~9)
 
Effective java
Effective javaEffective java
Effective java
 
Effective c++ chapter3, 4 요약본
Effective c++ chapter3, 4 요약본Effective c++ chapter3, 4 요약본
Effective c++ chapter3, 4 요약본
 
5 6 1
5 6 15 6 1
5 6 1
 
More effective c++ chapter1 2_dcshin
More effective c++ chapter1 2_dcshinMore effective c++ chapter1 2_dcshin
More effective c++ chapter1 2_dcshin
 
More effective c++ 챕터3~4ppt
More effective c++ 챕터3~4pptMore effective c++ 챕터3~4ppt
More effective c++ 챕터3~4ppt
 
Effective C++ 정리 chapter 3
Effective C++ 정리 chapter 3Effective C++ 정리 chapter 3
Effective C++ 정리 chapter 3
 
More effective c++ 항목30부터
More effective c++ 항목30부터More effective c++ 항목30부터
More effective c++ 항목30부터
 
Effective c++ 2
Effective c++ 2Effective c++ 2
Effective c++ 2
 

Similar to Effective c++ 1~8장

Api design for c++ ch3 pattern
Api design for c++ ch3 patternApi design for c++ ch3 pattern
Api design for c++ ch3 patternjinho park
 
Api design for c++ pattern
Api design for c++ patternApi design for c++ pattern
Api design for c++ patternjinho park
 
Effective cpp
Effective cppEffective cpp
Effective cppTonyCms
 
More effective c++ chapter4 이후 항목 29까지
More effective c++ chapter4 이후 항목 29까지More effective c++ chapter4 이후 항목 29까지
More effective c++ chapter4 이후 항목 29까지Dong Chan Shin
 
Effective C++ Chapter 3 Summary
Effective C++ Chapter 3 SummaryEffective C++ Chapter 3 Summary
Effective C++ Chapter 3 SummarySeungYeonChoi10
 
More Effective C++ 4주차
More Effective C++ 4주차More Effective C++ 4주차
More Effective C++ 4주차Injae Lee
 
Effective C++ Chapter 1 Summary
Effective C++ Chapter 1 SummaryEffective C++ Chapter 1 Summary
Effective C++ Chapter 1 SummarySeungYeonChoi10
 
Chapter5 ~ 6
Chapter5 ~ 6Chapter5 ~ 6
Chapter5 ~ 6Injae Lee
 
Mec++ chapter3,4
Mec++ chapter3,4Mec++ chapter3,4
Mec++ chapter3,4문익 장
 
Effective c++(chapter3,4)
Effective c++(chapter3,4)Effective c++(chapter3,4)
Effective c++(chapter3,4)문익 장
 
Chapter7~9 ppt
Chapter7~9 pptChapter7~9 ppt
Chapter7~9 pptInjae Lee
 
[0618구경원]초보 게임프로그래머를 위한 c++
[0618구경원]초보 게임프로그래머를 위한 c++[0618구경원]초보 게임프로그래머를 위한 c++
[0618구경원]초보 게임프로그래머를 위한 c++KyeongWon Koo
 
디자인패턴 1~13
디자인패턴 1~13디자인패턴 1~13
디자인패턴 1~13Shin heemin
 
Head first디자인패턴 1~13_희민_호준
Head first디자인패턴 1~13_희민_호준Head first디자인패턴 1~13_희민_호준
Head first디자인패턴 1~13_희민_호준HoJun Sung
 
Effective STL 1~4장 정리
Effective STL 1~4장 정리Effective STL 1~4장 정리
Effective STL 1~4장 정리Shin heemin
 
More effective c++ 2
More effective c++ 2More effective c++ 2
More effective c++ 2현찬 양
 
Effective c++ chapter1 2_dcshin
Effective c++ chapter1 2_dcshinEffective c++ chapter1 2_dcshin
Effective c++ chapter1 2_dcshinDong Chan Shin
 

Similar to Effective c++ 1~8장 (20)

Api design for c++ ch3 pattern
Api design for c++ ch3 patternApi design for c++ ch3 pattern
Api design for c++ ch3 pattern
 
Api design for c++ pattern
Api design for c++ patternApi design for c++ pattern
Api design for c++ pattern
 
Effective cpp
Effective cppEffective cpp
Effective cpp
 
MEC++ 5
MEC++ 5MEC++ 5
MEC++ 5
 
More effective c++ chapter4 이후 항목 29까지
More effective c++ chapter4 이후 항목 29까지More effective c++ chapter4 이후 항목 29까지
More effective c++ chapter4 이후 항목 29까지
 
Effective C++ Chapter 3 Summary
Effective C++ Chapter 3 SummaryEffective C++ Chapter 3 Summary
Effective C++ Chapter 3 Summary
 
More Effective C++ 4주차
More Effective C++ 4주차More Effective C++ 4주차
More Effective C++ 4주차
 
Effective C++ Chapter 1 Summary
Effective C++ Chapter 1 SummaryEffective C++ Chapter 1 Summary
Effective C++ Chapter 1 Summary
 
Chapter5 ~ 6
Chapter5 ~ 6Chapter5 ~ 6
Chapter5 ~ 6
 
MEC++ 3,4
MEC++ 3,4MEC++ 3,4
MEC++ 3,4
 
Mec++ chapter3,4
Mec++ chapter3,4Mec++ chapter3,4
Mec++ chapter3,4
 
Effective c++(chapter3,4)
Effective c++(chapter3,4)Effective c++(chapter3,4)
Effective c++(chapter3,4)
 
Chapter7~9 ppt
Chapter7~9 pptChapter7~9 ppt
Chapter7~9 ppt
 
[0618구경원]초보 게임프로그래머를 위한 c++
[0618구경원]초보 게임프로그래머를 위한 c++[0618구경원]초보 게임프로그래머를 위한 c++
[0618구경원]초보 게임프로그래머를 위한 c++
 
디자인패턴 1~13
디자인패턴 1~13디자인패턴 1~13
디자인패턴 1~13
 
Head first디자인패턴 1~13_희민_호준
Head first디자인패턴 1~13_희민_호준Head first디자인패턴 1~13_희민_호준
Head first디자인패턴 1~13_희민_호준
 
EC 789
EC 789EC 789
EC 789
 
Effective STL 1~4장 정리
Effective STL 1~4장 정리Effective STL 1~4장 정리
Effective STL 1~4장 정리
 
More effective c++ 2
More effective c++ 2More effective c++ 2
More effective c++ 2
 
Effective c++ chapter1 2_dcshin
Effective c++ chapter1 2_dcshinEffective c++ chapter1 2_dcshin
Effective c++ chapter1 2_dcshin
 

Effective c++ 1~8장

  • 2. C++에 왔으면 C++의 법을 따릅시다 Chapter 1
  • 3. 항목 1 : C++를 언어들의 연합체로 바라보는 안목은 필수 - C - 객체 지향 개념의 c++ - 템플릿 c++ - STL C++은 4가지의 하위 언어로 이루어져 있다. c++의 어떤 부분을 사용하냐에 따라 효과적인 규칙이 달라진다
  • 4. 항목 2 :#define을 쓰려거든 const, enum, inline을 떠올리자. 선행처리자보다 컴파일러를 더 가까이 하자 #define ASPECT_RATIO 1.653; 컴파일러에게 넘어가기전에 선행처리자가 밀어버리고 숫자 상수로 바꾸어 버림 -> ASPECT_RATIO는 컴파일러가 쓰는 기호테이블에 들어가지 않는다. -> 에러발생시에 햇갈리게된다. 해결법 : 매크로 대신 상수를 사용해라! Const double AspectRatio = 1.653; 상수타입의 데이터이기 때문에 컴파일러도 알고 기호테이블에 들어간다.
  • 5. 항목 2 :#define을 쓰려거든 const, enum, inline을 떠올리자. #define을 상수로 교체할때의 주의사항 1. 상수 포인터를 정의하는 경우. 포인터는 꼭 const로 선언해 주어야 하고, 이와 아울러 포인터가 가리키는 대상까지 const로 선언 Ex) const char * const authorName = “Scott Meyers”; const std::string authorName(“Scott Meyers”); // char* 기반보다 string객체를 사용하는게 좋다. 2. 클래스 멤버로 상수를 정의하는 경우 : 클래스 상수를 정의하는 경우 어떤 상수의 유효범위를 클래스로 한정하고자 할 때는 그 상수를 멤버로 만들어야 하는데, 그 상수의 사본 개수가 한 개를 넘지 못하게 하고 싶으면 정적(static) 멤버로 만들어야 한다.
  • 6. 항목 2 :#define을 쓰려거든 const, enum, inline을 떠올리자. Static은 헤더파일에서 선언하고 구현파일에서 정의한다. 나열자 둔갑술
  • 7. 항목 3 : 낌새만 보이면 const를 들이대 보자! 어떤 값이 불변이어야 한다는 제작자의 의도를 컴파일러 및 다른 프로그래머와 나눌 수 있는 수단 어떤 값을 변하게 하지 않게 하려면 const를 사용해야 한다. Const키워드가 *표의 왼쪽에 있으면 포인터가 가리키는 대상이 상수 Const키워드가 *표의 오른쪽에 있으면 포인터 자체가 상수. Const키워드가 *표의 양쪽에 있으면 가리키는 대상 및 포인터 둘 다 상수
  • 8. 항목 3 : 낌새만 보이면 const를 들이대 보자! Const와 STL반복자 : 어떤 반복자를 const로 선언하는 일은 포인터를 상수로 선언하는 것 : 만약 변경이 불가능한 객체를 가리키는 반복자가 필요하다면 const_iterator을 사용
  • 9. 항목 3 : 낌새만 보이면 const를 들이대 보자! 멤버 함수에 붙는 const 키워드의 역할은 “해당 멤버 함수가 상수 객체에 대해 호출될 함수“ 이런 함수가 중요한 이유 1. 클래스의 인터페이스를 이해하기 좋게 하기 위해서 - 그 클래스로 만들어진 객체를 변경할 수 있는 함수는 무엇이고, - 변경할 수 없는 함수는 무엇인가를 사용자쪽이 알아야 하기 때문에 2. 이 키워드를 통해 상수 객체를 사용할 수 있게 하자는 것
  • 10. 항목 3 : 낌새만 보이면 const를 들이대 보자! Const 키워드가 있고 없고의 차이만 있는 멤버 함수들은 오버로딩이 가능하다.
  • 11. 항목 3 : 낌새만 보이면 const를 들이대 보자! 비트 수준 상수성 어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 그 멤버 함수가 ‘const’임을 인정하는 개념 객체를 구성하는 비트들 중 어떤 것도 바꾸면 안된다는 것
  • 12. 항목 3 : 낌새만 보이면 const를 들이대 보자! 논리적 상수성 상수 멤버 함수라고 해서 객체의 한 비트도 수정할 수 없는 것이 아니라 일부 몇 비트 정도는 바꿀 수 있되, 그 것을 사용자측에서 알아채지 못하게만 하면 상수 멤버 자격이 있다.
  • 13. 항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자! 생성자에서는 객체의 모든 것을 초기화하자! “대입을 초기화와 햇갈리지 않는것이 가장 중요하다“ 왼쪽 값은 초기화가 아니라 대입이 되고, 오른쪽은 초기화가 되고있다. 왼쪽보다는 오른쪽이 훨씬 효율적이다.
  • 14. 항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자! theName, theAddress 및 thePhones에 대해 기본 생성자를 호출해서 초기화를 미리 해 놓은 후에 생성자에서 곧바로 새로운 값을 대입하고 있다. -> 먼저 호출된 기본 생성자에서 해 놓은 초기화는 헛짓이 됨.
  • 15. 항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자! theName은 name으로 부터 복사생성자에 의해 초기화 theAddress는 address로 부터 복사생성자에 의해 초기화 thePhones도 phones로 부터 복사생성자에 의해 초기화 멤버 초기화 리스트
  • 16. 항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자! 클래스 객체의 생성자 초기화 순서 - 기본 클래스는 파생클래스보다 먼저 초기화 - 클래스 데이터 멤버는 그들이 선언된 순서대로 초기화 찾기 힘든 버그를 만들어낼 수 있기 때문에 멤버 초기화 리스트에 넣은 멤버들의 순서를 클래스에 선언한 순서와 동일하게 맞추는것을 권장
  • 17. 항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자! 정적객체 : 자신이 생성된 시점부터 프로그램이 끝날 때까지 살아 있는 개체를 일컫는다. 정적 객체의 범주에 들어가는 것들 1. 전역 객체 2. 네임스페이스 유효 범위에서 정의된 객체 3. 클래스 안에서 static으로 선언된 객체 4. 함수 안에서 static으로 선언된 객체 5. 파일 유효범위에서 static으로 정의된 객체 함수 안에 있는 정적 객체는 지역 정적 객체라고 하며, 나머지는 비지역 정적 객체라고 한다.
  • 18. 항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자! 별개의 번역 단위에서 정의된 비지역 정적 객체들의 초기화 순서는 정해져있지 않다. 싱글톤 패턴을..잘 모르겠음…
  • 19. 생성자_소멸자 및 대입 연산자 Chapter 2
  • 20. 항목 5 : C++이 은근 슬쩍 만들어 호출하는 함수들 직접 선언하지 않는다면 컴파일러는 다음 함수들을 선언해준다. - 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자 (기본 생성자와 소멸자는 컴파일러에게 배후의 코드?의 자리를 마련한다) 특징 -기본 클래스의 소멸자가 비 가상 소멸자이면 파생클래스의 소멸자도 비 가상이다. -상수/참조 타입 멤버변수가 있으면 대입 연산자는 직접 정의해 줘야 한다. -기본 클래스에서 private으로 복사 대입연산자가 선언 되어있다면 파생 클래스에서 암시적으 로 만들어 주지 않는다.
  • 21. 항목 6 : 컴파일러가 만들어낸 함수가 필요 없으면 확실히 사용을 금하자 -실수로라도 복사생성자 및 복사 대입 연산자를 사용하고 싶지 않다면, private으로 선언해놓자. (하지만 friend함수에서 불릴 여지가 있다. 이런 경우 정의를 해놓지 않는다면 링크에러가 나타나게 된다.) 만일 컴파일 시점에서 에러처리를 해놓고 싶다면 -복사 생성자 및 복사 대입 연산자를 private으로 선언 하는 복사 방지 기본클래스를 만들어 상 속받아 사용하자.
  • 22. 항목 7 : 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선 언하자! -기본 클래스가 다형성을 가진 클래스일 때 비 가상함수를 사용한다면 기본 클래스의 포인터로 파생 클래스의 객체가 삭제될 때 파생 클래스가 소멸이 안 되는 문제 가 생긴다!! 즉, 다형성을 가진 기본 클래스에는 반드시 가상 소멸자를 선언해 주자! (단 기본클래스로 설계된 클래스가 아니거나 다형성을 갖도록 설계된 클래스가 아니라면 남발 하지 말자.)
  • 23. 항목 8 : 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 여러 소멸자에서 동시에 예외가 던져진다면 활성화된 예외가 축적 되 문제가 심각해 질것이다. -std::abort() : 예외가 발생하면 프로그램을 강제 종료 시킨다. -예외가 발생하면 로그찍고 진행한다.(프로그램이 올바르게 계속 실행될 보장이 있다면 해볼만 한 시도라고 한다.) 이는 둘다 적절한 해결방법이라고 보기 힘들다. -즉 애초에 예외처리를 소멸자에서 하지 말고 다른 함수에서 비롯되어야 한다는 뜻! (또한 사용자에게 함수를 호출 가능하게 하더라도 선택의 기회를 줌으로 부담을 덜어준다.)
  • 24. 항목 9 : 객체 생성 및 소멸 과정 중에는 절대로 가상함수를 호출하지 말자 -생성자에서 가상함수를 사용하게되면 파생 클래스의 객체를 생성할 때 먼저 불리는 기본 클래스의 생성자에서 가상함수가 호출되게 되면 아직 이 객체는 파생 클래스가 아닌 기본 클래스 타입이기 때문에 기본 클래스 타입의 가 상 함수가 호출 되게 된다! -기본 클래스 생성자의 호출시: 자식 클래스 멤버는 아직 초기화 되지 않은 상태이다. -기본 클래스 소멸자의 호출시: 자식 클래스의 멤버 변수는 이미 해제된 상태이다.
  • 25. 항목 9 : 객체 생성 및 소멸 과정 중에는 절대로 가상함수를 호출하지 말자 -이를 해결하기 위해 생성자에서는 비 가상함수를 사용하고 필요한 초기와 정보를 파생 클래스 에서 기본 클래스로 올려 보내준다.
  • 26. 항목 10 : C++의 대입 연산자는*this의 참조자를 반환하게 하자 클래스는 기본 타입과 되도록 똑같은 동작을 취하도록 설계하는 것이 좋다. - Int x, y, z; - x = y = z = 5; 와 같은 동작이 되도록 대입 연산은 참조자를 반환 하자 - T& operator=(const T& rhs) { …… return *this; } - +=, *= 등의 대입 연산자에도 동일하게 적용한다.
  • 27. 항목 11 : operator= 에서는 자기 대입에 대한 처리가 빠지지 않도록 하자 자기 대입이란 어떤 객체가 자기 자신에 대해 대입 연산자를 적용하는 것을 말한다. -이 경우 제대로 처리해 놓지 않는다면 문제가 발생할 수 있다. (중복 참조의 문제가 발생하는 것이다) 1.일치성 검사: 2.사본 복사 후 대입, 삭제: 원본을 기억하고 사본을 만든 뒤에 원본을 delete 해준다.
  • 28. 항목 12 : 객체의 모든 부분을 빠짐없이 복사하자 복사 생성자를 직접구현한 후에 추가로 데이터 멤버를 하나 추가하게되면 추가한 부분을 복사하지 못하는 현상이 발생한다.(컴파일러도 알려주지 않는다!) - 해당 클래스의 데이터 멤버를 모두 복사했는지 - 이 클래스가 상속한 기본 클래스의 복사함수(복사 생성자/복사 대입연산자)도 잘 호출해 주 는지 주의해야 한다.
  • 30. 항목 13 : 자원 관리에는 객체가 그만! 투자 객체(pInv)의 삭제가 실패할 수도 있다. 1. ‘…’ 어딘가에서 return문을 만나 도중 하차하는 경우 2. createInvestment 호출문과 delete문이 한 루프 안에서 continue 혹은 go to 를 만나게 되는 경우 3. 예외처리로 인해 delete를 거치지 않는 경우 자원을 객체에 넣고 그 자원 해제를 소멸자가 맡도록 하며, 그 소멸자는 실행 제어가 f를 떠날 때 호출 되도록 만드는 것
  • 31. 항목 13 : 자원 관리에는 객체가 그만! SmartPoint(스마트 포인터) New/delete와는 달리 가리키고 있는 대상에 대해 소멸자가 자동으로 delete를 불러주도록 설계 되어 있다.
  • 32. 항목 13 : 자원 관리에는 객체가 그만! 자원 관리에 객체를 사용하는 방법의 중요한 두 가지 특징 1. 자원을 획득한 후에 자원 관리 객체에게 넘긴다. 2. 자원 관리 객체는 자신의 소멸자를 사용해서 자원이 확실히 해제되도록 한다. Auto_ptr은 자신이 소멸될 떄 자신이 가리키고 있는 대상에 대해 자동으로 delete를 하기 때문에 어떤 객체를 가리키는 auto_ptr의 개수가 둘 이상이면 절대로 안된다. 만약 둘 이상이 된다면 자원이 두번 삭제되는 결과를 낳는다. Auto_ptr의 유별난 특성 - auto_ptr 객체를 복사하면 원본 객체는 null로 만든다.
  • 33. 항목 13 : 자원 관리에는 객체가 그만! TR1에서 제공되는 tr1::shared_ptr Auto_ptr을 쓸 수 없는 상황일아면 참조 카운팅 방식 스마트 포인터(RCSP)를 써라 : 특정한 어떤 자원을 가리키는 외부 객체의 개수를 유지하고 있다가 그 개수가 0이 되면 해당 자원 을 자동으로 삭제하는 스마트 포인터
  • 34. 항목 14 : 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자! RAll 객체가 복사될 때 어떤 동작으로 이루어져야 할까?
  • 35. 항목 14 : 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자! 1. 복사를 금지한다. -복사 연산을 private멤버로 만들어서 복사를 막을 순 있다. 2. 관리하고 있는 자원에 대해 참조 카운팅을 수행한다. 현재 tr1::shared_ptr방식으로 쓰이고 있다. 3. 관리하고 있는 자원을 깊은 복사로 수행 4. 관리하고 있는 자원의 소유권을 옮긴다.
  • 36. 항목 15 : 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자 ! daysHeld 함수는 Investment* 타입의 실제 포인터를 원하는데, tr1::shared_ptr<Investment> 타입의 객체를 넘기고 있어서 에러! RAII 클래스의 객체를 그 객체가 감싸고 있는 실제 자원으로 변환할 방법이 필요하다. 1. 명시적 변환 2. 암시적 변환
  • 37. 항목 15 : 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자 ! 1. 명시적 변환 Tr1::shared_ptr 및 auto_ptr은 명시적 변환을 수행하는 get이라는 멤버 함수를 제공 각 타입으로 만든 스마트 포인터 객체에 들어 있는 실제 포인터를 얻어 낼 수 있다.
  • 38. 항목 15 : 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자 ! 2. 암시적 변환 Tr1::shared_ptr 및 auto_ptr은 포인터 역참조 연산자도 오버로딩 하고 있다.
  • 39. 항목 16 : new 및 delete를 사용할 때는 형태를 반드시 맞추자! Std::string *stringArray = new std::string[100] Delete stringArray // 에러 생성할때 배열로 생성했으면 해제할때도 배열로 해제해 주어야 한다. Std::string *stringArray = new std::string[100] Delete[] stringArray // 성공 단일 객체와 객체의 배열이 사용하는 힙의 구조가 다르기 때문에 객체의 배열은 그 위치에 배열의 개수를 갖고 있고, 단일 객체는 실제 그 object를 갖고 있음
  • 40. 항목 17 : new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으 로 만들자. Int priority(); Void processWidget(std::Tr1::shared_ptr<Widget> pw, int priority); Process widget 함수는 동적 할당된 widget 객체에 대해 스마트 포인터를 사용하도록 만들어짐 processWidget(new Widget, priority()); // 컴파일 에러 포인터를 받는 tr1::shared_ptr의 생성자는 explicit로 선언되어 있기 때문에, ‘new Widget’ 표현 식에 의해 만들어진 포인터가 tr1::shared_ptr 타입의 객체로 바꾸는 암시적인 변환이 있을 리가 없다. processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority()); // 컴파일 성공
  • 41. 항목 17 : new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으 로 만들자. 컴파일러는 processWidget 호출 코드를 만들기 전에 우선 이 함수의 매개변수로 넘겨 평가하는 순서를 밟는다. - “new Widget” 표현식을 실행하는 부분 - Tr1::shared_ptr 생성자를 호출하는 부분 - Priority를 호출한다 - “new Widget”을 실행한다 - Tr1::shared_ptr 생성자를 호출한다. - 각각의 연산이 실행되는 순서는 컴파일러마다 다르기 때문에 포인터 유실이 될 수 있다. - Priority호출 부분에서 예외가 발생하게 되면 “new Widget”으로 만들어졌던 포인터가 유실 - Widget을 생성해서 스마트 포인터에 저장하는 코드를 별도의 문장 하나로 만들고, 그 스마트 포인터를 processWidget에 넘기는 것 Std::tr1::shraed_ptr<Widget> pw(new Widget); processWidget(pw, priority()); // 컴파일 성공 -> 자원 누출 걱정 없음
  • 43. 항목 18 : 인터페이스 설계는 제대로 쓰기 쉽게, 엉터리로 쓰기에 어렵게. 인터페이스 설계할 때 사용자의 입력 방향을 잡아줌으로 안정적인 설계가 가능하다. - Struct Day{explicit Day(int day)…} //Month, Year 도 동일하다. Data(const Month& m, const Day& d, const Year& y)); - Data d(Month(8), Day(4), Year(2017)); // 이와 같이 사용자가 헷갈리 여지를 최소화 해준다. 인터페이스를 설계할 때 일관성을 유지하는 것도 중요한 점이다. - STL 컨테이너 에서 각 자료구조의 길이를 구하는 size 나 length로 설계 되어있는것이 좋은 예이다. 사용자의 실수를 막고 사전에 봉쇄할 수 있는 인터페이스를 설계 할 수 있도록 지속적으로 고민하자. (tri::shared_ptr 에 대한 사용 이점을 설명해주고 있다. 이부분은 이해가 잘 가지 않아 좀더 공부가 필요하다.)
  • 44. 항목 19 : 클래스 설계는 타입 설계와 똑같이 취급하자. 클래스를 설계할 때, 새로운 타입을 설계하는 것과 같다. 12개의 질문을 던지고 있다. 1. 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하는가. 2. 객체 초기화에는 객체 대입과 어떻게 달라야 하는가? -초기화와 대입을 헷갈리지 말아야 한다. 각각에 해당하는 함수 호출이 다르기 때문 3. 새로운 타입으로 만든 객체가 값에 의해 전달 되는 경우에 어떤 의미를 줄 것인가? -값에 의한 전달은 복사 생성자에 의해 전달된다. 4.새로운 타입이 가질 수 있는 적법한 값에 대한 제약은 무엇으로 잡을 것인가? -생성자, 대입 연산자 등 각종 쓰기 함수는 불변속성이 중요하기 때문 5.기존 클래스 상속 계통망에 맞출것인가? -상속의 여부로 소멸자와 멤버 함수의 가상 비 가상함수를 맞춰 설계 해야한다.
  • 45. 항목 19 : 클래스 설계는 타입 설계와 똑같이 취급하자. 6.어떤 종류의 타임 변환을 허용할 것인가. -암시적(implicit) 변환과 명시적(explicit) 타입 변환의 설계를 고려해야 한다. 7.어떤 연산자와 함수를 두어야 의미가 있을까? -클래스에 선언할 함수이다. 어떤 것은 멤버함수로 어떤 것은 그렇지 않게 설계 될 것이다. 8.표준 함수들 중 어떤 것을 허용하지 말 것인가? -복사 생성자/ 복사 대입연산자를 허용하고 싶지 않을 때 private으로 선언 하는 것과 같다. 9.새로운 타입의 멤버에 대한 접근 권한을 어느 쪽에 줄 것인가? -private / protected/ public 10.선언 되지 않은 인터페이스 로 무엇을 둘 것인가? -만들 타입이 제공할 보장이 어떤 종류일까에 대한 질문이다. 클래스 구현에 있어서 제약으로 작용하게 된다.
  • 46. 항목 19 : 클래스 설계는 타입 설계와 똑같이 취급하자. 11. 새로 만드는 타입이 얼마나 일반적인가? -만일 타입 하나를 정의하는 것이 아닐지도 모른다. 이때에는 새로운 클래스 템플릿을 정의해야 할 것이다. 12.정말로 꼭 필요한 타입인가? -기존 클래스에 대해 기능 몇 가지 추가를 위해 파생 클래스를 새로 만드는 것 보다 비 멤버 함수나 템플릿을 몇 개 더 정의하는 것이 효율적이다. 클래스를 설계하기 전에 위 사항들을 계속 점검해 보자.
  • 47. 항목 20 : ‘값에 의한 전달‘ 보다 ‘상수 객체 참조자에 의한 전달'을 하자. C++은 기본적으로 값에 의한 전달이다. -값에 의한 전달은 복사 생성자를 호출하여 전달 된다! -뿐 만 아니라 소멸자도 호출 되게 된다. 이러한 비효율적 값 전달을 해결하기 위해 상수객체에 대한 참조자로 전달하게 설계한다. - bool validateStudent(Student s); 를 bool validateStudent(const Student& s); 로 바꾼다. 복사 손실 문제를 해결 할 수 있다. (파생 클래스의 객체가 기본클래스의 객체로 전달 될 때, 기본 클래스의 복사 생성자가 호출되어 파생 클래스의 객체로 동작하게 해주는 값들이 잘려나가는 현상)
  • 48. 항목 20 : ‘값에 의한 전달‘ 보다 ‘상수 객체 참조자에 의한 전달'을 하자. 항상 참조 전달이 좋은 것 만은 아니다. - 전달 타입이 기본타입일 경우에는 값에 의한 전달이 효율적일 때가 많다. -STL 반복자와 함수 객체도 마찬가지이다.(이때는 복사 효율을 높이고 복사 손실문제를 고려 해야한다) 크기가 작다고 값에 의한 전달이 효율적이지 않다. -사용자 정의형 타입일 경우 언제 크기가 달라질지 모르고, 그 타입이 가르키는 포인터의 대상까지 복사하는 작 업이 일어날 수 있다.
  • 49. 항목 21 : 함수에서 객체를 반환해야 할 경우에 참조자를 반환하지 말자. 참조전달을 남용해서는 큰일이다. 스택에서 생성한 객체의 참조반환 -반환 형이 참조일 때 이미 소멸된 객체의 참조를 반환할 가능 성이 있다. 힘에서 동적 생성한 객체의 참조반환 -함수 내에서 delete를 해주는 것 또한 문제가 된다. 생성자 호출을 줄이고자 했지만 두 방법 다 어렵다. 즉, 가능하면 값으로 전달하자. (새로운 객체를 반환하게 만드는 방식)
  • 50. 항목 22 :데이터 멤버가 선언될 곳은 private 영역임을 명심하자. Public 데이터 멤버는 왜 안 될까? -문법적 일관성에 문제가 있다. (사용자가 객체에 접근 가능한 방법이 모두 함수로 되어 있는 것이 일관성 있다) -읽기, 쓰기 접근에 대한 제어가 힘들다. (private으로 접근 방식을 유연하게 설계할 수 있다.) -protected도 사실 public 과 큰 차이가 없다고 볼 수 있다. (파생 클래스가 기본 클래스의 데이터 멤버에 높은 의존상태이다.) 즉, 요점은 캡슐화의 관점에서 쓸모 있는 접근 수준은 private이냐 아니냐 이다.
  • 51. 항목 23 :멤버 함수보다는 비 멤버, 비 프렌드 함수와 더 가까워 지자. 많은 캡슐화는 높은 유연성을 가지게 된다. -어떤 데이터를 접근하는 함수가 많을 수록 그 데이터 캡슐화의 정도는 낮아지는 걸 의미한다. 왼쪽은 멤버 함수들을 호출하는 멤버함수 / 오른쪽은 객체를 받아 멤버 함수들을 호출하는 비 멤버함수 이다. - 멤버 함수는 private데이터에 접근이 가능하지만 비 멤버(or 비 프렌드)함수는 private 데이터에 접근 할 수 없 다. (또한 아래 비 멤버 함수 같은 경우는 편의성 함수라고 볼 수 있다. 이런 것은 namespace로 헤더파일에 선언 해 놓으면 보다 높은 확장성을 가지게 된다.)
  • 52. 항목 24 :타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비 멤버 함수를 선 언하자. Rational 클래스에 대한 operator*를 모든 인자에 대해 암시적 타입 변환을 수행하고 싶다. -우선 Rational 클래스 자체와 관련이 있으니 operator*는 멤버함수로 구현해보자. const Rational operator* (const Rational& rhs) const; 로 선언. -Rational oneEighth(1, 8); Rational oneHalf(1, 2); Rational result = oneHalf * oneEighth; //잘 된다. 하지만 우리는 혼합형 수치 연산도 하고싶다. 즉 Rational 을 int 같은 값과도 곱하고 싶다! result = oneHalf *2; //잘 된다. result = 2 * oneHalf; //에러!
  • 53. 항목 24 :타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비 멤버 함수를 선 언하자. 다음과 같은 형태로 파악해보자 result = oneHalf *2; //잘 된다. result = 2 * oneHalf; //에러! result = oneHalf .operator*(2); // oneHalf 객체는 operatior* 함수를 멤버로 갖고 있는 클래스의 인스턴스! result = 2.operator*(oneHalf); // 하지만 2는 아니다. 이때 컴파일러는 비 멤버 버전의 operator* 를 찾아보지만 있을리가없다. (Rational::operator* 선언문에서 lhs 부분에 this를 사용하기 때문이다.) -rhs부분은 Rational temp(2);로 암시적 형 변환을 해준다. (명시적 형변환으로 선언 되있다면 이것도 에러!) 즉, 이때는 비 멤버 함수로 처리해야 한다. const Rational operator* (const Rational& lhs, const Rational& rhs) const; { return Rational(lhs.numerator()*rhs.numerator(), lhs.denominator()*rhs.denominator()); //캡슐화된 접근 }
  • 54. 항목 25 :예외를 던지지 않는 swap에 대한 지원도 생각해보자 잘 동작하는 swap을 만들어야한다. -표준 std::swap를 살펴 보자 Template<typename T> void swap(T& a, T& b) { T temp(a); a = b; b = temp; } -T가 복사연산을 지원하면 제대로 동작한다. -3번의 복사 연산 비용이 들어감이 예상된다.
  • 55. 항목 25 :예외를 던지지 않는 swap에 대한 지원도 생각해보자 - 표준에서 제공하는 swap이 우리 클래스 및 클래스 템플릿에 대해 납득할만한 효율을 보이면 그냥 사용하자. 하지만 그렇지 않다면 다음과 같은 방법이 있다. - Swap함수를 해당 타입의 publi멤버 함수로 만든다.(단 예외가 던져지면 안 된다) - 관련된 클래스 혹은 템플릿이 들어있는 namespace와 같은 장소에 비멤버 swap를 만들어 위에서 만든 public swap함수에서 호출하게 한다. - 새로운 클래스를 만들고 있다면 그클래스에 대한 std::swap의 특수화 버전을 만들고 이에 대해서도 swap 멤버 함수를 호출 하도록 하자. - 후반부는 사실 거의 이해가 가지 않았다.. 더 공부하도록 하자
  • 57. 항목 26 : 변수 정의는 늦출 수 있는데까지 늦추는 근성을 발휘하자 If문에서 예외가 발생하면 encryped는 사용하지 않는 변수가 됨. -> encrypted 객체의 생성과 소멸에 대해 비용을 내야 하므로 메모리 낭비
  • 58. 항목 26 : 변수 정의는 늦출 수 있는데까지 늦추는 근성을 발휘하자 예외 발생했을때의 낭비를 피하기 위해 늦게 선언을 했음. 하지만 초기화를 해주지 않아 효율적이지가 않다!
  • 59. 항목 26 : 변수 정의는 늦출 수 있는데까지 늦추는 근성을 발휘하자 1. 어떤 변수를 사용해야 할 때가 오기 전까지 그 변수의 정의를 늦춰야 함 2. 초기화 인자를 손에 넣기 전까지 정의를 늦출 수 있는지 둘러봐야함 -> 쓰지도 않을 객체가 만들어졌다 없어지는 일이 생기지 않으며, 불필요한 기본 생성자 호출이 일어나지 않고, 그 변수의 의미가 명확한 상황에서 초기화가 이루어지기 때문에, 변수의 쓰임새를 문서화하는데도 큰 도움이 된다.
  • 60. 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자 C 스타일의 캐스트 (T) 표현식 // 표현식 부분을 T타입으로 캐스팅 함수 방식 캐스트 T(표현식) // 표현식 부분을 T타입으로 캐스팅 -> 이 두 방식은 구형 스타일의 캐스트 C++은 4가지의 새로운 캐스트 연산자를 독자적으로 제공 Const_cast<T>(표현식) Dynamic_cast<T>(표현식) Reinterpret_cast<T>(표현식) Static_cast<T>(표현식)
  • 61. 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자 Const_cast : 객체의 상수성을 없애는 용도로 사용 Dynamic_cast : ‘안전한 다운 캐스팅'을 할 때 사용하는 연산자 주어진 객체가 어떤 클래스 상속 계통에 속한 특정 타입인지 안인지를 결정 신경쓰일 정도로 런타임 비용이 높은 캐스트 연산자 Reinterpret_cast : 포인터를 int로 바꾸는 하부 수준 캐스팅을 위해 만들어진 연산자 Static_cast : 암시적 변환[비상수 객체를 상수 객체로 바꾸거나, int를 double로 바꾸는]을 강제로 진행할 때 사용
  • 62. 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자 *this를 windo로 캐스팅 이에 따라 onResize는 Window::onResize가 됨. 함수 호출이 이루어지는 객체가 현재의 객체가 아님 *this의 기본 클래스 부분에 대한 사본이 임시적으로 만들어진다 onResize는 사본에서 호출된것 ! onResize를 수정하면 사본에서는 수정되지만, 현재 객체에선 수정되지 않는다.
  • 63. 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자 현재 객체애 대고 onResize의 기본 클래스 버전을 호출하도록 만들면 현재 객체에서 수정이 됨
  • 64. 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자 Dynamic_cast는 이러쿵 저러쿵 탈도 많은 연산자 깊이가 4인 단일 상속 계통에 속한 어떤 객체에 대해 이 연산자를 적용할 때, Strcmp가 최대 4번까지 불릴 수 있다는 것. 상속 깊이가 더 깊거나 다중 상속에서 쓰이게 되면 비용이 더 커진다.
  • 65. 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자 Dynamic_cast 연산자를 쓰고 싶어지는 때가 있긴 하다. 파생 클래스 객체임이 분명한 녀석이 있어서 이에 대해 파생 클래스의 함수를 호출하고 싶은데, 그 객체를 조작할 수 있는 수단으로 기본 클래스의 포인터밖에 없을 경우는 적지 않게 생김 1. 파생클래스 객체에 대한 포인터를 컨테이너에 담아둠으로써 각 객체를 기본 클래스 인터페이스를 통해 조작할 필요를 아예 없애 버리는 것. 2. 기본 클래스에 함수를 구현해서 가상 함수로 제공하는 것
  • 66. 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자 1. 파생클래스 객체에 대한 포인터를 컨테이너에 담아둠으로써 각 객체를 기본 클래스 인터페이스를 통해 조작할 필요를 아예 없애 버리는 것.
  • 67. 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자 2. 기본 클래스에 함수를 구현해서 가상 함수로 제공하는 것
  • 68. 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자 정말 피해야 하는 설계 -폭포식 dynamic_cast
  • 69. 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자 이것만은 잊지 말자 • 다른 방법이 가능하다면 캐스팅은 피해라 • 캐스팅이 어쩔 수 없이 필요하다면, 함수 안에 숨길 수 있도록 해라 • 구형 스타일의 캐스트를 쓰려거든 c++ 스타일의 캐스트를 선호하라.
  • 70. 항목 28 : 내부에서 사용하는 객체에 대한 ‘핸들'을 반환하는 코드는 되도록 피하자
  • 71. 항목 28 : 내부에서 사용하는 객체에 대한 ‘핸들'을 반환하는 코드는 되도록 피하자 클래스 데이터 멤버는 아무리 숨겨봤자 그 멤버의 참조자를 반환하는 함수들의 최대 접근도에 따라 캡슐화 정도가 정해진다. “Ulhc와 lrhc는 privat로 선언되어 있지만, 이들의 참조자를 반환하는 upperLeft 와 lowerRight함수가 public” 이므로 실질적으로는 public 멤버이다. 사용자는 사각형을 정의하는 꼭짓점 쌍을 읽을 수는 있지만 쓸 수는 없게 된다.
  • 72. 항목 29 : 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자! 예외 안전성이 확보되는 코드란? 예외가 발생시에 문제점 1. 메모리 누수 - mutex unlock이 실행되지 못한다. 문제점 2. 기존 m_bgImage가 삭제된채로 throw된다.
  • 73. 항목 29 : 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자! 문제점 1. 메모리 누수 - mutex unlock이 실행되지 못한다. 해결 방법 자원 관리 클래스를 사용할 것 자원 관리 클래스의 소멸시 소멸자 호출로 뮤텍스 반환
  • 74. 항목 29 : 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자! 문제점 2. 기존 m_bgImage가 삭제된채로 throw된다. 해결 방법 1. 기본적인 보장 – 문제 없이 프로그램을 죽지 않게는 하지만 내부적으로 어떤 문제가 있을 지는 알 수 없음, 따로 예외처리 루틴을 만들어서 체크 2. 강력한 보장 – 마치 아무 일도 없었다는 듯이 정상 실행 -> 오류 발생시 이전상태로 실행 3. 예외 불가 보장 – 절대 예외가 발생하지 않음을 보장
  • 75. 항목 29 : 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자! 대체로 기본적인 보장 혹은 강력한 보장을 고른다. 강력한 보장을 지원하는 설계 방식 - 복사 후 맞바꾸기 기법 원본이 아닌 사본을 생성한 뒤, 사본을 수정 문제없이 사본이 완성되면, 원본과 바꿔치기
  • 76. 항목 30 : 인라인 함수는 미주알고주알 따져서 이해해두자! 함수처럼 보이고 함수처럼 동작하는데다가, 매크로보다 훨씬 안전하고 쓰기 좋은 인라인 함수 인라인 함수의 아이디어는 함수 호출문을 그 함수의 본문으로 바꿔치기 하자는 것! 인라인 함수의 장점 ! 대체적으로 컴파일러 최적화는 함수 호출이 없는 코드가 연속적으로 이어지는 구간에 적용되도록 설계 인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별 최적화를 걸기가 용이해진다. 인라인 함수의 단점! 메모리가 제한된 컴퓨터에서 아무 생각 없이 인라인을 남발했다가는 프로그램 크기가 그 기계에서 쓸 수 있는 공간을 넘어 버릴 수 있다. 가상 메모리를 쓰는 환경일지라도 인라인 함수로 인해 부풀려진 코드는 성능의 걸림돌이 되기 쉽다 페이징 횟수가 늘어나고, 명령어 캐시 적중률이 떨어질 가능성도 높아진다.
  • 77. 항목 30 : 인라인 함수는 미주알고주알 따져서 이해해두자! 인라인 함수는 대체적으로 헤더 파일에 들어 있어야 하는게 맞다. - 대부분의 빌드 환경에서 인라인을 컴파일 도중에 수행하기 때문에! 정말 간단한 함수라고 할 지라도 가상 함수 호출 같은 것은 절대로 인라인할수 없다! Virtual의 의미가 “어떤 함수를 호출할지 결정하는 작업을 실행중에 한다"라는 뜻이고, Inline의 의미가 “함수 호출 위치에 호출된 함수를 끼워넣는 작업을 프로그램 실행 전에 한다"라는 뜻이므로 가상함수호출에는 절대로 인라인을 해주지 않는다.
  • 78. 항목 30 : 인라인 함수는 미주알고주알 따져서 이해해두자! 결론 : 인라인 함수가 실제로 인라인 되느냐 안되느냐의 여부는 전적으로 개발자가 사용하는 빌드 환경에 달렸다. 인라인 함수로 선언된 함수를 함수 포인터를 통해 호출하는 경우도 대개 인라인되지 않는다.
  • 79. 항목 30 : 인라인 함수는 미주알고주알 따져서 이해해두자! 이것만은 잊지 말자 • 함수 인라인은 작고 자주 호출되는 함수에 대해서만 하는 것으로 묶어두자. 디버깅 및 라이브러리의 바이너리 업그레이드가 용이해지고, 자칫 생길 수 있는 코드 부풀림 현상이 최소화되며, 프로그램의 속력이 더 빨라질 수 있는 여지가 최고로 많아진다. * 함수 템플릿이 대개 헤더 파일에 들어간다는 일반적인 부분만 생각해서 이들을 inline으로 선언하지 말자
  • 80. 항목 31 : 파일 사이의 컴파일 의존성을 최대로 줄이자. 이렇게 되면 birthdate와 theAddress가 바뀔 때마다 Person클래스가 컴파일이 됨 -> 퍼포먼스 낭비 = 분리 시켜야 낭비가 되지 않는다.(한쪽은 인터페이스만 제공하고, 한쪽은 그 인터페이스의 구현을 맡도록
  • 81. 항목 31 : 파일 사이의 컴파일 의존성을 최대로 줄이자. Person의 사용자는 생일, 주소, 이름 등의 자질구레한 세부사항과는 완전히 갈라지게 된다. 인터페이스와 구현을 둘로 나누는 열쇠는 ‘정의부에 대한 의존성'을 ‘선언부에 대한 의존성’으로 바꾸어 놓는다. 컴파일 의존성을 최소화하는 핵심 원리 !
  • 82. 항목 31 : 파일 사이의 컴파일 의존성을 최대로 줄이자. Person의 사용자는 생일, 주소, 이름 등의 자질구레한 세부사항과는 완전히 갈라지게 된다. 인터페이스와 구현을 둘로 나누는 열쇠는 ‘정의부에 대한 의존성'을 ‘선언부에 대한 의존성’으로 바꾸어 놓는다. 컴파일 의존성을 최소화하는 핵심 원리 !
  • 83. 상속 그리고 객체지향 설계 Chapter 6
  • 84. 항목 32 : public 상속 모형은 반드시 is-a를 따르자 DrivedClass is a BaseClass 기본 클래스는 파생 클래스보다 일반적인 버전이다. - 새는 날수 있지만 팽귄은 날수 없다?! (기본 클래스에서 Fly() 함수를 선언했다면 파생 클래스인 팽귄은 에러를 던지게 정의.) (기본 클래스에서 Fly() 함수를 선언하지 말고 파생 클래스에서 날수 있는 새 클래스를 만든다.) 두번째 방법이 컴파일에서 에러가 나타나게 되 더 효율이 좋다. 기본 클래스에 적용되는 모든 것들이 파생 클래스에 그대로 적용되므로 신중한 설계가 필요하다.
  • 85. 항목 33 : 상속된 이름을 숨기는 일은 피하자 상속에서 이름 영역을 항상 고려하자. 기본 클래스에서 void mf3(); void mf3(double); 로 선언하고 파생 클래스에서 mf3(); 와 mf3(x); 를 모두 호출 했을 때, mf3(x); 는 찾지 못하고 에러가 나타난다. (파생 클래스의 유효범위에서 대상을 찾았는데 인자가 다르기 때문이다.) 이를 해결하고 싶다면 - using선언 사용 ( using Base::mf3(); ) private 상속에서는 불가능 하다. -전달함수 사용 ( virtual void mf3() { Base::mf3(); }; 와 같이 부모의 이름 영역에서 호출한다)
  • 86. 항목 34 : 인터페이스 상속과 구현 상속의 차이를 제대로 파악하고 구별하자 인터페이스 상속 : 멤버의 함수 선언만을 상속 받는 것. 구현상속 : 멤버 함수의 정의도 같이 상속 받는 것. -순수 가상 함수: 파생 클래스에게 함수의 인터페이스만을 물려주는 것 (파생 클래스에서 반드시 구현 해야한다) -비 순수(단순) 가상함수: 단순 가상함수를 선언하는 목적은 파생 클래스로 하여금 함수의 인터페 이스 뿐만 아니라 그 함수의 기본 구현도 물려받게 하자는 것이다. (기본 구현체가 있는 것은 상속받는 새로운 객체가 재정의를 해야 함을 잊을 수 있는 위험이 있다.) 이는 비 순수 가상함수를 순수 가상함수로 만들고 비 가상함수로 default 함수를 만들어 사용한다. virtual void fly(const Airport& destination){ defaultFly(destination);}
  • 87. 항목 35 : 가상함수 대신 쓸 것들도 생각해 두는 자세를 길러두자 Non-virtual Interface(NVI). -public 비 가상 멤버 함수를 통해 private 가상 함수를 간접적으로 호출하게 만드는 기법. -이때 쓰이는 비 가상 함수를 가상함수의 wrapper 라고 한다.
  • 88. 항목 35 : 가상함수 대신 쓸 것들도 생각해 두는 자세를 길러두자 Non-virtual Interface(NVI). -사전/사후 동작이 파생 클래스에 모두 공통인 경우(자원 초기화등) -인터페이스 함수는 언제 동작 할지 정한다. -구현 가상 함수는 어떻게 구현할지 정한다. 함수 포인터로 구현한 전략 패턴 -NVI와 유사하다 -stategy패턴의 단순한 버전(예제 코드는 이해하기 어려웠다.. 공부하자) (구현 함수 대신 외부 함수 포인터를 멤버 변수로 받아 사용/ 파생클래스에서 재정의하여 사용한 다.) -실행 함수를 동적으로 변경 할 수 있다. -하지만 클래스 멤버가 아니므로 private 접근이 불가능 하다.
  • 89. 항목 35 : 가상함수 대신 쓸 것들도 생각해 두는 자세를 길러두자 tr1::function으로 구현한 전략패턴(거의 이해가 가지 않음.) -개념은 함수 포인터와 비슷하다. -융통성 있는 활용이 가능하다. 고전적인 전략 패턴 -특정 기능을 수행하는 클래스를 외부에 만들고 원래 클래스가 그 클래스에 대한 객체를 가르키 는 포인터를 가지고 있고, 포인터는 생성자에서 초기화해 사용한다. -전체적인 구조는NVI와 유사하다.
  • 90. 항목 36 : 상속받은 비가상 함수를 파생 클래스에 재정의하는 것은 절대 금물! 비 가상 함수는 정적 바인딩으로 묶인다. -상속 받은 비가상 함수를 재정의하면 문제가 생긴다. (기본 클래스 형 포인터로 파생 클래스를 가르킬 경우 기본 클래스의 비 가상함수가 호출 된다!) 즉, 상속 시킨 비 가상 함수는 불변의 동작을 구현한 것이다.
  • 91. 항목 37 : 어떤 함수에 대해서도 상속 받은 기본 매개변수 값은 절대로 재정의 하지 말자! 이 항목은 ‘기본 매개변수 값을 가진 가상 함수를 상속하는 경우'에 대한 이야기이다. (기본 매개변수는 함수 선언에서 default 인자를 설정하는 것이다.) (이때 기본 매개 변수 값은 정적 바인딩 된다.) - 가상 함수의 호출은 동적으로 바인딩 된다. - 비 가상 함수의 재정의 문제와 똑같은 문제가 발생한다. 기본 클래스 형 포인터로 자식 클래스의 객체를 가르키고 함수를 호출하면, 기본 클래스의 매개변 수를 이용해서 파생 클래스의 함수가 호출 되는 문제가 발생한다. (NVI 방식을 통해 해결 할 수 있다.) - 파생 클래스에서 재정의할 수 있는 가상함수를 private 멤버로 두고 이 가상 함수를 호출하는 publice 비가상 함수를 기본 클래스에 만들어 두는 것이다.
  • 92. 항목 38 : “has-a”혹은 “is-implemented-in-terms-of”를 모형화 할 때는 객체 합성을 이용하자. 객체 합성은 클래스를 멤버 변수로 갖고 있는 것이다. has-a : ~는 ~를 가짐 (person has a address) (사물을 본뜬 응용 영역) Is-implemented-in-terms-of: ~는 ~로 구현됨 (시스템 구현을 위한 구현 영역) 다른 객체의 함수를 사용하여 구현에 적용한다. std::list 로 set 구현의 예제가 있지만 완벽한 이해는 공부가 필요하다..
  • 93. 항목 39 : private 상속은 심사 숙고해서 구사하자. Private 상속은 public 상속과 대조적으로 파생 클래스의 객체를 기본 클래스의 객체로 변환하지 않는다. - 기본 클래스로 부터 물려받은 멤버는 파생 클래스에서 모조리 private 멤버가 된다. - Is-implemented-in-terms-of 구현이라고 볼 수 있다. (구현만 물려받고 인터페이스는 안 됀다 라는 의미) - 공백 클래스(정적 데이터 멤버가 없는 클래스)에 최적화 기법에 사용된다. (공백 기본 클래스 최적화(EBO empty base optimization)) - 파생 클래스 쪽에서 기본 클래스의 protected멤버에 접근 해야할 경우 혹은 상속받은 가상함 수를 재정의 해야 할 경우에는 private상속이 나름대로 의미가 있다. - 보통은 public 상속+객체합성 조합이 더 많이 사용된다.
  • 94. 항목 39 : 다중 상속은 심사 숙고해서 사용하자. 다중 상속은 모호성 문제가 가장 위험하다. -둘 이상의 기본 클래스로부터 똑같은 이름의 함수 (또는 typedef 등)을 물려 받을때의 모호성. 다중 상속의 죽음의 마름모 꼴을 살펴보자 이때 File에 데이터멤버가 있다면 IOFile 에서는 데이터 멤버를 중복 생성할 우려가 있다. -이때 File 클래스를 가상 기본 클래스로 만드는 것!
  • 95. 항목 39 : 다중 상속은 심사 숙고해서 사용하자. 가상 기본 클래스 상속 -비용이 비싸다. -굳이 사용해야 한다면 가상 기본 클래스가 될 클래스에 데이터를 최대한 넣지 말자. 다만 다중 상속을 적법하게 사용할 경우가 있다. - 인터페이스 클래스로 부터 public 상속을 시킴과 동시에 구현을 돕는 클래스로부터 private 상 속을 시키는 때이다. (예제의 이해를 위해 더욱 공부하자)
  • 97. 항목 41 : 템플릿 프로그래밍의 천릿길도 암시적 인터페이스와 컴파일 타임 다형성 부터. 객체 지향 프로그래밍의 세계를 회전시키는 축은 명시적 인터페이스와 런타임 다형성 명시적 인터페이스 : 클래스에서는 그 인터페이스의 소스 코드를 보면 이것이 어떤 형태인지 확인할 수 있다. 암시적 인터페이스 : 템플릿은 그 템플릿이 구현된 모습을 보고 어떤 인터페이스가 필요한지를 유추할 수 있다. 템플릿화 될 T는 operator >, != size()등이 정의되어있어야 함을 알 수 있다. 컴파일 타임 다형성 : 템플릿을 인스턴스화 하는 과정에서 T의 타입이 결정된다 (어떤 매개변수가 들어가느냐에 따라 호출이 달라진다.
  • 98. 항목 42 : typename의 두 가지 의미를 제대로 파악하자 Class와 typename의 차이는 없다! Typename을 쓰지 않으면 안되는 때가 있다. 의존이름 : 템플릿 내의 이름 중에 템플릿 매개변수에 종속된 것 중첩 의존 이름 : 의존 이름이 어떤 클래스 안에 중첩되어 있는 경우 비의존 이름 : 템플릿 매개변수가 어떻든 상관없는 타임
  • 99. 항목 42 : typename의 두 가지 의미를 제대로 파악하자 의존 이름은 동일한 클래스(C) 가 있다면 정상동작 하지 않을 수가 있다. (C::const_iterator가 진짜 타입인지 아닌지 알아낼 방법은 없다!!) (const_iterator라는 이름을 가진 정적 데이터 멤버가 C에 들어있다고 볼 수도 있다. 따라서 앞에 typename을 붙여주어 타입이라고 지정해주어야 한다.
  • 100. 항목 43 : 템플릿으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자 완전 템플릿 특수화 템플릿이 클래스 A를 상속받은 클래스 B가 A의 함수를 사용한다면 컴파일러가 이 클래스 A가 어디서 파생된 것인지를 찾을 수 없어 에러가 발생한다. Template <typename T> Class A { Public: Void SendClear(); } Class B : public A<T> { void func() { SendClear(); } }
  • 101. 항목 43 : 템플릿으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자 기본 클래스에 접근할 때는 아래의 세 가지 방법을 사용한다. This->SendClear();
  • 102. 항목 43 : 템플릿으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자 기본 클래스에 접근할 때는 아래의 세 가지 방법을 사용한다. Using A<T>::SendClear;
  • 103. 항목 43 : 템플릿으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자 기본 클래스에 접근할 때는 아래의 세 가지 방법을 사용한다. A<T>::SendClear(); 이 방법은 추천하지 않는다. 호출되는 함수가 가상 함수인 경우에는, 명시적 한정을 해 버리면 가상 함수 바인딩이 무시되기 때문
  • 104. 항목 44 : 매개변수에 독립적인 코드는 템플릿으로. 행과 열의 크기를 나타내는 상수만 빼면 두 함수는 완전히 똑같아!! 템플릿을 포함한 프로그램이 코드 비대화를 일으키는 일반적인 형태 중 하나
  • 105. 항목 44 : 매개변수에 독립적인 코드는 템플릿으로 매개변수(template<T>에서 T)에 독립적인 코드는 템플릿에서 분리 Base template class를 만들어 독립적인 함수를 넣고 이를 상속받아 자식클래스에서 매개변수를 사용하는 부분 작성
  • 106. 항목 45 : “호환되는 모든 타입”을 받아들이는 데는 멤버 함수 템플릿이 직방 멤버 함수 템플릿은 어떤 클래스의 멤버 함수를 찍어내는 템플릿! 일반 포인터는 부모 클래스 포인터로 자식 클래스를 가르 킬 수 있다. 하지만 shared_ptr<>같은 템플릿 에서는 지원하지 않는다. 이를 해결하기 위해 멤버 함수 템플릿을 사용한다. 생성자를 무제한 만들기 어렵기 때문에, 생성자를 만들어내는 템플릿 모든 T타입 및 모든 U타입에 대해서 SmartPtr<T> 객체가 SmartPtr<U>로 부터 생성될 수 있다. SmartPtr<U>의 참조자를 매개변수로 받아들이는 생성자가 SmartPtr<T> 안에 들어있기 때문 일반화 복사 생성자 라고 한다.
  • 107. 항목 45 : “호환되는 모든 타입”을 받아들이는 데는 멤버 함수 템플릿이 직방 SmartPtr<T>의 일반화 복사 생성자는 호환되는 타입의 매개변수를 넘겨받을 때만 컴파일된다.
  • 108. 항목 45 : “호환되는 모든 타입”을 받아들이는 데는 멤버 함수 템플릿이 직방 멤버 함수 템플릿은 코드 재사용만큼이나 기특하고 훌륭한 기능이지만, c++언어의 기본 규칙까지는 바꾸지 않는다. 이것만은 잊지 말자! 호환되는 모든 타입을 받아 들이는 멤버 함수를 만들려면 멤버 함수 템플릿을 사용하자. 일반화된 복사 생성 연산과 일반화된 대입 연산을 위해 멤버 템플릿을 선언했다 하더라도, 보통의 복사 생성자와 복사 대입 연산자는 여전히 직접 선언해야 한다.
  • 109. 항목 46 : 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의
  • 110. 항목 46 : 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의 Operator* 함수를 호출 할 때 어떤 함수를 호출하려는지 알 수 없다. Operator*의 템플릿 T가 무엇인지 모르기 때문에.. 컴파일러는 템플릿 인자 추론을 통해 인자를 찾아 oneHalf의 T가 int형인지는 알 수 있지만, 2의 경우는 처리를 할 수 없다. 템플릿 인자 추론 과정에서 암시적 변환을 고려하지 않기 때문이다
  • 111. 항목 46 : 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의 클래스 템플릿 안에 프렌드 함수를 넣어 두면 함수 템플릿으로서의 성격을 주지 않고 특정한 함수 하 나를 나타낼 수 있다는 사실
  • 112. 항목 46 : 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의 클래스 템플릿 안에 프렌드 함수를 넣어 두면 함수 템플릿으로서의 성격을 주지 않고 특정한 함수 하 나를 나타낼 수 있다는 사실 정의가 없기 때문에 링커가 찾지를 못한다.
  • 113. 항목 46 : 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의 Operator* 함수의 본문을 선언부와 붙여서 해결!
  • 114. 항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 STL은 기본적으로 컨테이너 및 반복자, 알고리즘의 템플릿으로 구성되어 있지만, 이 외에 유틸리티라고 불리는 템플릿도 몇 개 들어있다. 이들 중 하나가 advance라는 이름의 템플릿인데, 지정된 반복자를 지정된 거리만큼 이동시키는 것 += 연산을 지원하는 반복자는 임의 접근 반복자 밖에 없기 때문에, 구현이 안된다.
  • 115. 항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 STL Iterator – Input iterator 입력 반복자 전진만 가능하고, 한번에 한칸씩만 이동하며, 자신이 가리키는 위치에서 읽기만 가능하다. 읽을 수 있는 횟수도 한번 입력 파일에 대한 읽기 전용 파일 포인터를 본떠서 만들었다. STL반복자는 각 반복자가 지원하는 연산에 따라 5개의 범주로 나뉜다.
  • 116. 항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 STL Iterator – output iterator 출력 반복자 입력 반복자와 비슷하지만 출력용 앞으로만 가고, 한 번에 한칸씩만 이동하며, 자신이 가리키는 위치에서 쓰기만 가능 쓸 수 있는 횟수가 한번 출력 반복자는 출력 파일에 대한 쓰기 전용 파일 포인터를 본떠서 만들었다.
  • 117. 항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 STL Iterator – forward iterator 순방향 반복자 입력 반복자와 출력 반복자가 하는 일은 기본적으로 다 할 수 있다. 자신이 가리키고 있는 위치에서 읽기와 쓰기를 동시에 할 수 있다. 이 반복자는 multi-pass 알고리즘에 사용할 수 있다.
  • 118. 항목 46 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 STL Iterator – bidirectional iterator 양반향 반복자 순방향 반복자에 뒤로 갈 수 있는 기능을 추가한 것이다. STL list에 쓰는 반복자가 이 범주에 속한다.
  • 119. 항목 46 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 STL Iterator – random access iterator 임의 접근 반복자 양방향 반복자에"반복자 산술 연산" 수행 기능을 추가하였다. 반복자의 임의의 거리만큼 앞뒤로 이동시키는 일을 상수 시간 안에 할 수 있다. Vector, deque, string에 사용하는 반복자는 임의 접근 반복자이다.
  • 120. 항목 46 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 C++ 표준 라이브러리에는 지금까지 말씀드린 다섯 개의 반복자 범주 각각을 식별하는 데 쓰이는 “태그(tag) 구조체"가 정의되어 있다.
  • 121. 항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 반복자들이 종류마다 가능한 것이 있고 불가능한 것이 잇다는 점을 안이상 구현할때 신경을 써야 함 최소 공통 분모 전략 : 반복자를 주어진 횟수만큼 반복적으로 증가시키거나 감소시키는 루프를 돌리는 것 -> 선형 시간이 걸린다는 것은 쉽게 예측할 수 있다. 상수 시간의 반복자 산술 연산을 쓸 수 있는 임의 접근 반복자 입장에서는 손해 Iter부분이 임의 접근 반복자인지를 판단할 수 있어야 한다. -> 어떤 타입에 대한 정보를 얻어내야 함 -> 특성정보를 사용한다
  • 122. 항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 특성 정보(traits) : 컴파일 도중에 어떤 주어진 타입의 정보를 얻을 수 있게 하는 객체를 지칭하는 개념 Deque의 반복자는 임의 접근 반복자 이므로 다음과 같은 형태
  • 123. 항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 특성 정보(traits) : 컴파일 도중에 어떤 주어진 타입의 정보를 얻을 수 있게 하는 객체를 지칭하는 개념 list의 반복자는 양방향반복자 이므로 다음과 같은 형태
  • 124. 항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 포인터 타입의 반복자를 지원하기 위해 iterator_traits는 포인터 타입에 대한 부분 템플릿 특수화 버전을 제공하고 있다. 다른 사람이 사용하도록 열어 주고 싶은 타입 관련 정보를 확인한다 그 정보를 식별하기 위한 이름을 선택한다 지원하고자 하는 타입 관련 정보를 담은 템플릿 및 그 템플릿의 특수화 버전을 제공한다.
  • 125. 항목 48 : 템플릿 메타 프로그래밍, 하지 않겠는가? 템플릿 메타프로그래밍(TMP)은 컴파일 도중에 실행되는 템플릿 기반의 프로그램을 작성하는 일 TMP의 강점 1. TMP를 쓰면 다른 방법으로는 까다롭거나 불가능한 일을 굉장히 쉽게 할 수 있다. 2. 템플릿 메타프로그램은 c++ 컴파일이 진행되는 동안에 실행되기 때문에, 기존 작업을 런타임 영역에서 컴파일 타 임 영역으로 전환할 수 있다. TMP에는 반복의미의 진정한 루프는 없기 때문에, 재귀를 사용해서 루프의 효과를 나타낸다. TMP의 루프는 재귀 함수 호출을 만들지 않고 재귀식 템플릿 인스턴스화를 한다.
  • 126. New 와 delete를 내 맘대로 Chapter 8
  • 127. 항목 49 : new 처리자의 동작 원리를 제대로 이해하자. operator new 함수가 메모리 할당 요청을 맞추지 못할 때 예외를 던지게 되어 있다. 이 예외를 던지기 전에 사용자 쪽에서 지정할 수 잇는 에러 처리 함수를 우선적으로 호출하도록 되어 있는데 이 함수가 new처리자(new-handler)라고 한다. set_new_handler가 받아들이는 new_handler타입의 매개변수는 요구된 메모리를 operator new 가 할당하지 못했을 때 operator new가 호출할 함수의 포인터 이다.
  • 128. 항목 49 : new 처리자의 동작 원리를 제대로 이해하자. new처리자의 용도 1.사용 할 수 있는 메모리를 더 많이 확보한다. (프로그램 시작 시 따로 메모리를 잡아놓고 new 처리자 호출시 사용하는 방법) 2.다른 new 처리자를 설치 (현재의 new 처리자가 메모리 할당을 해주지 못할 때 다른 new 처리자를 호출해 해결한다.) 3.new 처리자의 설체를 제거 (set_new_handler 에 널 포인터를 넘긴다) 4.예외를 던진다. (bad-alloc 또는 이에 파생된 타입의 예외를 던진다.) 5.복귀하지 않는다. (abort 혹은 exit 를 호출한다.)
  • 129. 항목 50 : new와 delete를 언제 바꿔야 좋은 소리를 들을지 파악해 두자. 왜 new와 delete를 오버라이딩 해야 하는가? 1.잘못된 힙 사용을 탐지 하기 위해 (버퍼오버 플로우 같이 메모리 할당 영역의 침범을 막는 등) 2.효율 향상을 위해 (범용성을 위한 기본 new/delete는 사용자 정의용보다 효율이 떨어질 수 있다.) 3.동적 할당 메모리의 실제 사용에 관한 통계 정보를 수집하기 위해 (할당된 메모리의 블록크기/ 사용기간 분포/ 메모리 사용 패턴 등) 4.할당 및 해제 속력을 높이기 위해 (역시 사용자 정의 버전이 특정 타입의 객체에 맞춰 설계되어 있으면 기본 제공 타입보다 빠를 것 이다.) 5.기본 메모리 관리자의 공간 오버헤드를 줄이기 위해
  • 130. 항목 50 : new와 delete를 언제 바꿔야 좋은 소리를 들을지 파악해 두자. 왜 new와 delete를 오버라이딩 해야 하는가? 6.적당히 타협한 기본 할당자의 바이트 정렬 동작을 보장하기 위해. (컴파일러에 따른 바이트 정렬을 사용자 정의 버전으로 바꿈으로 수행성능을 올릴 수 있다.) 7.임의의 관계를 맺고 잇는 객체들을 한 군데 나란히 모아 놓기 위해 8.그때 그때 원하는 동작을 수행하도록 하기 위해 요점은 개발자 스스로 사용자 정의 new/delete를 작성하는데 여러 타당한 이유가 있다.
  • 131. 항목 51 : new및 delete를 작성 할 때 따라야 할 기존의 관례를 잘 알아 두자 -관례적으로 operator new 함수는 메모리 할당을 반복해서 시도하는 무한 루프를 가져야 하고, 메모리 할당 요구를 만족시킬 수 없을 때 new 처리자를 호출해야 하며, 0바이트에 대한 대책도 있어야 한다. 클래스 전용 버전은 자신이 할당하기로 예정된 크기보다 더 큰 메모리 블럭에 대한 요구도 처리 해야 한다. -operator delete 함수는 널 포인터가 들어왔을 때 아무 일도 하지 않아야 합니다. 클래스 전용 버 전의 경우에는 예정 크기보다 더 큰 블록을 처리해야 한다.
  • 132. 항목 52 : 위치지정 new를 작성한다면 위치지정delete 도 같이 준비하자. -operator new 함수의 위치지정 버전을 만들 땐, 이 함수와 짝을 이루는 위치지정 버전으 operator delete 함수도 꼭 만들어 주자. 메모리 누출 현상을 겪을 수 있다. -new 및 delete의 위치지정 버전을 선언할 때는, 의도한 바도 아닌데 이들의 표준 버전이 가려지 는 일이 생기지 않도록 주의해야 한다.