EFFECTIVE C++ 정리
Chapter 2
생성자 소멸자 대입연산자
C++이 몰래 만든 함수들
ITEM 5
Item 5: C++이 몰래 만든 함수들
• 직접 선언하지 않으면 저절로 선언하는 함수들
• 기본 생성자
• 복사 생성자
• 복사 대입 연산자
• 소멸자
• 위의 함수가 필요한 경우 컴파일러가 선별하여 생성
Item 5: C++이 몰래 만든 함수들
• 기본 생성자와 소멸자
• 컴파일러의 뒷 작업(배후의 코드)을 처리하는 공간
• 기본 클래스, 비정적 데이터 멤버의 생성자 / 소멸자 호출
• 부모 클래스의 소멸자가 가상 소멸자가 아니라면
• 자식 클래스의 소멸자도 비 가상 소멸자로 만들어 진다
• 유저가 만든 생성자가 하나라도 있다면
• 컴파일러는 기본 생성자를 만들지 않는다.
Item 5: C++이 몰래 만든 함수들
• 복사 생성자 / 복사 대입 연산자
• 원본 객체의 비정적 데이터를 사본 객체에 복사하는 기능
• 자동으로 만들어지는 복사기능
• 멤버 변수가 사용자 정의 타입이면 복사 생성자/대입 연산자 호출
• 멤버 변수가 Built-in 타입이면 비트를 그대로 복사
• 상수/참조 타입 멤버변수가 있으면 대입 연산자 안 만들어줌
• 부모의 대입 연산자가 private면 대입 연산자 안 만들어 줌
자동 생성 함수 봉인 방법
ITEM 6
Item 6: 자동 생성 함수 봉인 방법
• 복사되지 않는 객체 만드는 방법
• 복사 생성자, 대입 연산자의 자동 생성을 봉인한다.
• private로 선언하고, 정의하지 않는다.
• private이므로 외부에서 접근 불가능! (friend는?)
• 정의하지 않았으므로 복사 사용하면, 링킹 에러
• 컴파일 시점에 원천 봉쇄하고 싶다면?
• 복사 생성자/ 대입 연산자를 private로 하는 복사 방지 클래스를 만든다.
• 복사 방지 클래스를 상속받는다.
• 최적화/다중상속 이슈는 뒤로 미룸 ^^
부모님께 가상 소멸자
하나 놔드려야 겠어요
ITEM 7
Item 7: 부모클래스에 가상 소멸자 놓기
• C++ 규정 saying
• “부모 클래스 포인터를 통해서 자식 클래스가 삭제될 때,
부모 클래스에 가상 소멸자가 없다면  Undefined Behavior”
• 일반적인 상황
• 부모 클래스의 소멸자가 호출
• 자식 클래스의 소멸자 호출되지 않음
• 자식 클래스의 종료 처리 및 해제 동작 안함  ERROR & LEAK
Item 7: 부모클래스에 가상 소멸자 놓기
• 부모님께 가상 소멸자를 넣어드립시다
• 가상함수는 동적 타입을 추적하여 올바른 함수를 호출하게 한다.
• 가상 소멸자도 동적 타입에 올바른 동작을 수행한다.
• 자식 소멸자 부터 상속 순으로 최상위 부모 소멸자 까지 호출됨
Item 7: 부모클래스에 가상 소멸자 놓기
• 그렇다고 아무 때나 가상함수를 남발하면 안 된다.
• 부모가 아니거나, 부모 역할(virtual 함수)이 필요 없는 경우
• virtual을 쓰는 순간 클래스에는 가상함수 테이블,
인스턴스에는 가상함수 포인터가 생긴다.
• 불필요한 메모리 사용
• 부모 클래스에 가상함수가 있을 때, 가상 소멸자를 사용하자.
Item 7: 부모클래스에 가상 소멸자 놓기
• 가상 소멸자가 없는 클래스를 상속받지 말자.
• STL이나 std::string등에는 가상 소멸자가 없으므로 주의
• 추상 클래스를 만들 때 이렇다할 가상함수가 없다면?
• 순수 가상 소멸자만 만들어 두면 된다.
• 가상 소멸자는 최상위까지 반드시 호출되므로 정의도 구현해야한다.
• 모든 부모클래스가 다형성을 지원하는 것은 아니다.
• 앞에서도 말했지만, 가상 소멸자를 선택할 때 주의를 기울여라
소멸자에서 try catch
ITEM 8
Item 8: 소멸자에서 try catch
• 만약 여러 개의 소멸자에서 동시에 예외를 던진다면?
• 클래스 컨테이너에서 반복자를 통해 소멸자를 호출하는 경우
• Catch는 저 멀리, 활성화된 예외가 축적된다.
• C++에서 감당하기 어려운 상태가 되어버림.
• 불완전 종료 or Undefine Behavior…
Item 8: 소멸자에서 try catch
• 소멸자 내부에서 try하려면 catch도 안에서 해라.
• 오류나면 그대로 종료하는 방법 std::abort()
• 문제는 없지만 try catch를 쓰는 의미가?
• 오류나면 로그만 찍고 계속 진행하는 방법
• 무엇이 잘못되었는지 확실히 알기 어렵다.
• 오류 이후에도 프로그램이 올바르게 실행 되는 것이 보장돼야 한다.
• Catch는 오류에 대해서 적절한 대처를 하기 위함인데
어느 쪽도 적절해보이지 않는다.
Item 8: 소멸자에서 try catch
• 예외를 처리하는 부분을 소멸자에 넣지 마라
• 다른 함수로 빼서 사용자에게 맡긴다.
• Ex) Delete하기 전에 Release를 반드시 하도록 요구.
• 에러나면 사용자 탓^^
생성/소멸자에서 가상함수
ITEM 9
Item 9: 생성/소멸자에서 가상함수
• 생성자에서 가상함수를 사용하면 안 된다!
• 상속받은 클래스의 생성자/소멸자를 호출하면
• 최상위 부모클래스부터 해당 클래스까지 정해진 순서대로 생성/소멸자가 호출
• 상위 클래스의 생성자/소멸자 호출시점에서
• 해당 클래스는 현재 진행중인 호출 스택의 부모 타입 처럼 인식된다.
• 런타임 타입 추론을 이용하는 함수들에서도 동일하게 처리됨
• dynamic_cast , typeid 등
• 때문에 이 시점에 가상함수를 쓰면 해당 부모 타입의 가상함수가 호출된다.
Item 9: 생성/소멸자에서 가상함수
• 생성/소멸자 호출 시점에서 객체의 타입을 변경시키는 이유
• 생성자의 경우
• 자식 클래스의 멤버는 아직 초기화되지 않은 상태.
• 소멸자의 경우
• 자식 클래스의 멤버 변수는 이미 해제된 상태
• 여기서 멤버 함수를 호출하는 것은 위험하다.
Item 9: 생성/소멸자에서 가상함수
• 대처 방법
• 생성자에서는 비 가상 함수만 사용한다.
• 필요한 초기화 정보를 자식클래스에서 부모클래스로 올려준다.
class ChildClass : public ParentClass
{
public:
ChildClass(int arg)
: ParentClass(createLogString(arg))//초기화 리스트를 통해서 부모 생성자에 인자 전달
{...}
protected:
static std::string createLogString(int arg);
/* 정적 함수를 사용하면 멤버변수와 독립적으로 사용가능,
초기화 문제 해결. 생성자 호출 전에 호출가능 */
};
대입 연산자의 리턴형식
ITEM 10
Item 10: 대입 연산자의 리턴 형식
• 대입 연산은 chaining 이 가능 (일종의 convention)
• int x, y, z;
• x = y = z = 15; //x = (y = (z = 15));
• 대입 연산의 결과(좌변)가 반환 되어야 가능
• T& operator=(const T& rhs){ … return *this }
• 모든 형태의 대입 연산자에서 동일하게 적용
• +=, -=, *= …
자기 대입 처리
ITEM 11
Item 11: 자기 대입 처리
• 자기 대입
• 어떤 객체가 자기 자신에 대해 대입 연산자를 적용하는 것
• a[i] = a[j]; *px = *py; //충분히 자기대입 가능한 상황
• 중복참조에서 발생하는 자기대입의 가능성을 고려하자
• 자원 관리 객체를 사용할 때 특히 주의
Widget& operator=( const Widget& rhs ) //위험한 대입연산자의 예
{
delete m_Data;
m_Data = new Data(*rhs.m_Data);
...
return *this;
}
Item 11: 자기 대입 처리
• 대입연산의 예외에 대한 안정성을 구축하자.
• 상세한 내용은 item 29로 가시오 
• 올바른 순서를 갖추는 것 만으로 자기대입 처리를 가능하게!
Widget& operator=( const Widget& rhs )
{
Data* tempData = m_Data; //원래 데이터를 임시 저장
m_Data = new Data(*rhs.m_Data); //사본으로 멤버변수 대체
delete tempData; //임시 데이터 삭제
...
return *this;
}
Item 11: 자기 대입 처리
• 같은 객체인지 체크하고 싶다고?
• 쓸데없는 복사가 일어나는 건 맞다.
• 하지만 자기 대입이 일어날 확률은 vㅔ리 낮다.
• 동일성 체크는 대입 연산 할 때마다 해야 한다.
• 코드 비대, 연산 처리 분기발생, CPU 선행처리, 파이프라이닝 효율 저하…
• 체크의 비용 >>>> 가끔 복사 비용
Item 11: 자기 대입 처리
• 비기 copy & swap
• void Swap(Widget& rhs) // *this와 rhs의 데이터 스왑
• Swap에서 큰 비용 없이 데이터 전환이 가능하다면
• 문제 없고 깔끔한 자기대입 처리가능
Widget& operator=( const Widget& rhs )
{
Widget temp(rhs); //rhs의 사본을 생성하고
Swap(temp); //사본과 this의 데이터를 스왑
...
return *this;
}
복사는 확실하게
ITEM 12
Item 12: 복사는 확실하게
• 복사 생성자/대입 연산자를 직접 만들어 쓰는 경우
• 복사하지 않은 멤버변수가 있어도 컴파일러가 말해주지 않는다.
• 아래 항목을 충족시켰는지 체크할 것
• 해당 클래스의 데이터 맴버를 모두 복사했는가?
• 상속한 모든 부모클래스의 복사 생성자/ 대입 연산자를 호출했는가?
Item 12: 복사는 확실하게
• 상속받은 부모 클래스의 복사 생성자/대입연산자 호출 방법
• 초기화 리스트로 부모 객체의 복사 생성자 호출
• 부모 객체의 대입 연산자 직접 호출
ChildClass(const ChildClass& rhs)
: ParentClass(rhs)
{
//Do something
}
ChildClass& operator=( const ChildClass& rhs )
{
ParentClass::operator=( rhs );
//Do Something
return *this;
}
Item 12: 복사는 확실하게
• 복사 생성자  대입 연산자 서로 사용할 수 있을까?
• 비슷한 코드니까 둘 중 하나만 만들고 가져다 쓰면 안될까?
• 대입 연산자에서 복사 생성자를 호출하는 경우
• 이미 만들어진 객체를 다시 생성한다?
• 복사 생성자에서 대입 연산자를 호출하는 경우
• 대입 연산은 이미 초기화된 객체에만 사용가능
• 아직 초기화 되지 않은 객체를 대입 연산한다?
• 겹치는 부분을 별도의 멤버함수로 처리할 수는 있다.

Effective c++ 챕터 2 정리

  • 1.
    EFFECTIVE C++ 정리 Chapter2 생성자 소멸자 대입연산자
  • 2.
    C++이 몰래 만든함수들 ITEM 5
  • 3.
    Item 5: C++이몰래 만든 함수들 • 직접 선언하지 않으면 저절로 선언하는 함수들 • 기본 생성자 • 복사 생성자 • 복사 대입 연산자 • 소멸자 • 위의 함수가 필요한 경우 컴파일러가 선별하여 생성
  • 4.
    Item 5: C++이몰래 만든 함수들 • 기본 생성자와 소멸자 • 컴파일러의 뒷 작업(배후의 코드)을 처리하는 공간 • 기본 클래스, 비정적 데이터 멤버의 생성자 / 소멸자 호출 • 부모 클래스의 소멸자가 가상 소멸자가 아니라면 • 자식 클래스의 소멸자도 비 가상 소멸자로 만들어 진다 • 유저가 만든 생성자가 하나라도 있다면 • 컴파일러는 기본 생성자를 만들지 않는다.
  • 5.
    Item 5: C++이몰래 만든 함수들 • 복사 생성자 / 복사 대입 연산자 • 원본 객체의 비정적 데이터를 사본 객체에 복사하는 기능 • 자동으로 만들어지는 복사기능 • 멤버 변수가 사용자 정의 타입이면 복사 생성자/대입 연산자 호출 • 멤버 변수가 Built-in 타입이면 비트를 그대로 복사 • 상수/참조 타입 멤버변수가 있으면 대입 연산자 안 만들어줌 • 부모의 대입 연산자가 private면 대입 연산자 안 만들어 줌
  • 6.
    자동 생성 함수봉인 방법 ITEM 6
  • 7.
    Item 6: 자동생성 함수 봉인 방법 • 복사되지 않는 객체 만드는 방법 • 복사 생성자, 대입 연산자의 자동 생성을 봉인한다. • private로 선언하고, 정의하지 않는다. • private이므로 외부에서 접근 불가능! (friend는?) • 정의하지 않았으므로 복사 사용하면, 링킹 에러 • 컴파일 시점에 원천 봉쇄하고 싶다면? • 복사 생성자/ 대입 연산자를 private로 하는 복사 방지 클래스를 만든다. • 복사 방지 클래스를 상속받는다. • 최적화/다중상속 이슈는 뒤로 미룸 ^^
  • 8.
    부모님께 가상 소멸자 하나놔드려야 겠어요 ITEM 7
  • 9.
    Item 7: 부모클래스에가상 소멸자 놓기 • C++ 규정 saying • “부모 클래스 포인터를 통해서 자식 클래스가 삭제될 때, 부모 클래스에 가상 소멸자가 없다면  Undefined Behavior” • 일반적인 상황 • 부모 클래스의 소멸자가 호출 • 자식 클래스의 소멸자 호출되지 않음 • 자식 클래스의 종료 처리 및 해제 동작 안함  ERROR & LEAK
  • 10.
    Item 7: 부모클래스에가상 소멸자 놓기 • 부모님께 가상 소멸자를 넣어드립시다 • 가상함수는 동적 타입을 추적하여 올바른 함수를 호출하게 한다. • 가상 소멸자도 동적 타입에 올바른 동작을 수행한다. • 자식 소멸자 부터 상속 순으로 최상위 부모 소멸자 까지 호출됨
  • 11.
    Item 7: 부모클래스에가상 소멸자 놓기 • 그렇다고 아무 때나 가상함수를 남발하면 안 된다. • 부모가 아니거나, 부모 역할(virtual 함수)이 필요 없는 경우 • virtual을 쓰는 순간 클래스에는 가상함수 테이블, 인스턴스에는 가상함수 포인터가 생긴다. • 불필요한 메모리 사용 • 부모 클래스에 가상함수가 있을 때, 가상 소멸자를 사용하자.
  • 12.
    Item 7: 부모클래스에가상 소멸자 놓기 • 가상 소멸자가 없는 클래스를 상속받지 말자. • STL이나 std::string등에는 가상 소멸자가 없으므로 주의 • 추상 클래스를 만들 때 이렇다할 가상함수가 없다면? • 순수 가상 소멸자만 만들어 두면 된다. • 가상 소멸자는 최상위까지 반드시 호출되므로 정의도 구현해야한다. • 모든 부모클래스가 다형성을 지원하는 것은 아니다. • 앞에서도 말했지만, 가상 소멸자를 선택할 때 주의를 기울여라
  • 13.
  • 14.
    Item 8: 소멸자에서try catch • 만약 여러 개의 소멸자에서 동시에 예외를 던진다면? • 클래스 컨테이너에서 반복자를 통해 소멸자를 호출하는 경우 • Catch는 저 멀리, 활성화된 예외가 축적된다. • C++에서 감당하기 어려운 상태가 되어버림. • 불완전 종료 or Undefine Behavior…
  • 15.
    Item 8: 소멸자에서try catch • 소멸자 내부에서 try하려면 catch도 안에서 해라. • 오류나면 그대로 종료하는 방법 std::abort() • 문제는 없지만 try catch를 쓰는 의미가? • 오류나면 로그만 찍고 계속 진행하는 방법 • 무엇이 잘못되었는지 확실히 알기 어렵다. • 오류 이후에도 프로그램이 올바르게 실행 되는 것이 보장돼야 한다. • Catch는 오류에 대해서 적절한 대처를 하기 위함인데 어느 쪽도 적절해보이지 않는다.
  • 16.
    Item 8: 소멸자에서try catch • 예외를 처리하는 부분을 소멸자에 넣지 마라 • 다른 함수로 빼서 사용자에게 맡긴다. • Ex) Delete하기 전에 Release를 반드시 하도록 요구. • 에러나면 사용자 탓^^
  • 17.
  • 18.
    Item 9: 생성/소멸자에서가상함수 • 생성자에서 가상함수를 사용하면 안 된다! • 상속받은 클래스의 생성자/소멸자를 호출하면 • 최상위 부모클래스부터 해당 클래스까지 정해진 순서대로 생성/소멸자가 호출 • 상위 클래스의 생성자/소멸자 호출시점에서 • 해당 클래스는 현재 진행중인 호출 스택의 부모 타입 처럼 인식된다. • 런타임 타입 추론을 이용하는 함수들에서도 동일하게 처리됨 • dynamic_cast , typeid 등 • 때문에 이 시점에 가상함수를 쓰면 해당 부모 타입의 가상함수가 호출된다.
  • 19.
    Item 9: 생성/소멸자에서가상함수 • 생성/소멸자 호출 시점에서 객체의 타입을 변경시키는 이유 • 생성자의 경우 • 자식 클래스의 멤버는 아직 초기화되지 않은 상태. • 소멸자의 경우 • 자식 클래스의 멤버 변수는 이미 해제된 상태 • 여기서 멤버 함수를 호출하는 것은 위험하다.
  • 20.
    Item 9: 생성/소멸자에서가상함수 • 대처 방법 • 생성자에서는 비 가상 함수만 사용한다. • 필요한 초기화 정보를 자식클래스에서 부모클래스로 올려준다. class ChildClass : public ParentClass { public: ChildClass(int arg) : ParentClass(createLogString(arg))//초기화 리스트를 통해서 부모 생성자에 인자 전달 {...} protected: static std::string createLogString(int arg); /* 정적 함수를 사용하면 멤버변수와 독립적으로 사용가능, 초기화 문제 해결. 생성자 호출 전에 호출가능 */ };
  • 21.
  • 22.
    Item 10: 대입연산자의 리턴 형식 • 대입 연산은 chaining 이 가능 (일종의 convention) • int x, y, z; • x = y = z = 15; //x = (y = (z = 15)); • 대입 연산의 결과(좌변)가 반환 되어야 가능 • T& operator=(const T& rhs){ … return *this } • 모든 형태의 대입 연산자에서 동일하게 적용 • +=, -=, *= …
  • 23.
  • 24.
    Item 11: 자기대입 처리 • 자기 대입 • 어떤 객체가 자기 자신에 대해 대입 연산자를 적용하는 것 • a[i] = a[j]; *px = *py; //충분히 자기대입 가능한 상황 • 중복참조에서 발생하는 자기대입의 가능성을 고려하자 • 자원 관리 객체를 사용할 때 특히 주의 Widget& operator=( const Widget& rhs ) //위험한 대입연산자의 예 { delete m_Data; m_Data = new Data(*rhs.m_Data); ... return *this; }
  • 25.
    Item 11: 자기대입 처리 • 대입연산의 예외에 대한 안정성을 구축하자. • 상세한 내용은 item 29로 가시오  • 올바른 순서를 갖추는 것 만으로 자기대입 처리를 가능하게! Widget& operator=( const Widget& rhs ) { Data* tempData = m_Data; //원래 데이터를 임시 저장 m_Data = new Data(*rhs.m_Data); //사본으로 멤버변수 대체 delete tempData; //임시 데이터 삭제 ... return *this; }
  • 26.
    Item 11: 자기대입 처리 • 같은 객체인지 체크하고 싶다고? • 쓸데없는 복사가 일어나는 건 맞다. • 하지만 자기 대입이 일어날 확률은 vㅔ리 낮다. • 동일성 체크는 대입 연산 할 때마다 해야 한다. • 코드 비대, 연산 처리 분기발생, CPU 선행처리, 파이프라이닝 효율 저하… • 체크의 비용 >>>> 가끔 복사 비용
  • 27.
    Item 11: 자기대입 처리 • 비기 copy & swap • void Swap(Widget& rhs) // *this와 rhs의 데이터 스왑 • Swap에서 큰 비용 없이 데이터 전환이 가능하다면 • 문제 없고 깔끔한 자기대입 처리가능 Widget& operator=( const Widget& rhs ) { Widget temp(rhs); //rhs의 사본을 생성하고 Swap(temp); //사본과 this의 데이터를 스왑 ... return *this; }
  • 28.
  • 29.
    Item 12: 복사는확실하게 • 복사 생성자/대입 연산자를 직접 만들어 쓰는 경우 • 복사하지 않은 멤버변수가 있어도 컴파일러가 말해주지 않는다. • 아래 항목을 충족시켰는지 체크할 것 • 해당 클래스의 데이터 맴버를 모두 복사했는가? • 상속한 모든 부모클래스의 복사 생성자/ 대입 연산자를 호출했는가?
  • 30.
    Item 12: 복사는확실하게 • 상속받은 부모 클래스의 복사 생성자/대입연산자 호출 방법 • 초기화 리스트로 부모 객체의 복사 생성자 호출 • 부모 객체의 대입 연산자 직접 호출 ChildClass(const ChildClass& rhs) : ParentClass(rhs) { //Do something } ChildClass& operator=( const ChildClass& rhs ) { ParentClass::operator=( rhs ); //Do Something return *this; }
  • 31.
    Item 12: 복사는확실하게 • 복사 생성자  대입 연산자 서로 사용할 수 있을까? • 비슷한 코드니까 둘 중 하나만 만들고 가져다 쓰면 안될까? • 대입 연산자에서 복사 생성자를 호출하는 경우 • 이미 만들어진 객체를 다시 생성한다? • 복사 생성자에서 대입 연산자를 호출하는 경우 • 대입 연산은 이미 초기화된 객체에만 사용가능 • 아직 초기화 되지 않은 객체를 대입 연산한다? • 겹치는 부분을 별도의 멤버함수로 처리할 수는 있다.