[Effective C++] 항목 5 ~ 8
항목 5 : C++가 은근슬쩍 만들어 호출해버리는 함수들에 촉각을 세우자.
- 직접 선언하지 않으면 생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 컴파일러가 저절로 선언해준다.
- 암시적 생성
- 얕은 복사를 하는 것들
- 기본 생성, 소멸자 : 컴파일러에게 “배후의 코드”를 깔 수 있는 자리 마련.
ex) 기본 클래스 및 비정적 데이터 멤버의 생성자, 소멸자 호출
- 소멸자는 이 클래스가 상속한 기본 클래스의 소멸자가 가상 소멸자가 아니면 역시 비가상 소멸자로 만들어진다.
- 복사 생성자, 복사 대입 연산자 : 원본 객체의 비정적 데이터를 사본 객체 쪽으로 그냥 복사.
- 복사 생성자와 복사 대입 연산자는 근본적으로 동작 원리가 같다. 하지만 복사 대입 연산자가 재대로 동작하려면 최종 결과 코드가 적법하고 이치에 닿아야 한다.
- 둘 중 어느 검사도 통과하지 못하면 컴파일러는 operator=의 자동 생성을 거부한다.
ex) 복사 대입 연산자를 정의 없이 사용하려고 할 시, 클래스 내 비정적 상수 혹은 참조자는 값이 변할 수 없으므로 컴파일이 거부된다.
- 복사 대입 연산자를 private로 선언한 기본 클래스의 파생 클래스는 암시적 복사 대입 연산자를 가질 수 없다.
항목 6 : 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해버리자.
- 컴파일러가 생성하는 함수는 모두 public 멤버가 된다.
- 사용을 금할 시 private 멤버로 명시적 선언
- 해당 클래스의 멤버 함수 및 프랜드(friend) 함수가 호출될 수 있다. »+ 해결 : 정의를 하지 않음으로써 링크 시점에 에러가 뜨게 한다.
- 링크 시점 에러를 컴파일 시점 에러로 옮기는 방법 존재(에러 탐지는 나중으로 미루는 것보다 미리하는 것이 좋다.)
- 금하고자 하는 함수들을 private로 선언하되 별도의 기본 클래스에 넣고 이것으로부터 적용코자 하는 클래스를 파생시킨다.
-
컴파일러가 생성한 복사 함수는 기본 클래스의 대응 버전을 호출하게 되어 있다.
- 해당 기본 클래스(Uncopyable)의 기술적 이슈
- 상속은 public일 필요가 없다.(32, 39)
- 소멸자는 가상 소멸자가 아니어도 된다.(7)
- 데이터 멤버가 전혀 없으므로 공백 기본 클래스 최적화 기법이 먹힐 수 있다.(39)
- 그러나 기본 클래스이므로 해당 기법 적용시 다중 상속으로 갈 가능성이 있다.(40)
- 다중 상속시 공백 기본 클래스 최적화가 돌아가지 못할 때가 종종 있다.(30_
- 부스트 라이브러리(55)에 같은 구실의 클래스(noncopyable)가 있다.
항목 7 : 다형성을 가진 기본클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자.
- C++ 규정 : 기본 클래스 포인터를 통해 파생 클래스 객체가 삭제될 때 그 기본 클래스에 비가상 소멸자가 들어 있으면 프로그램 동작은 미정의 사항.
- 가상 함수를 하나라도 가진 클래스는 가상 소멸자를 가지는 게 대부분 맞다.
- 기본 클래스로 의도하지 않은 클래스에 대해 소멸자를 가상으로 선언하는 건 좋지 않다.
- 가상 함수를 하나라도 가지는 클래스는 vtbl을 가지는 vptr을 가지므로 객체의 크기가 커진다.
- 다른 언어로 선언된 동일한 자료구조와의 호환성이 없어진다.(vptr을 만들 수 없기 때문)
- 가상 소멸자가 없는 클래스를 기본 클래스로 사용하면 문제가 생길 수 있다.
- 어떤 클래스를 추상 클래스로 만들고자 하고 마땅히 넣을 만한 순수 가상함수가 없을 시
- 추상 클래스는 본래 기본 클래스로 쓰일 목적, 기본 클래스는 가상 소멸자를 가져야 한다.
- 순수 가장 소멸자를 선언한다.
- 가상 소멸자의 호출 매커니즘이 기본 클래스 소멸자를 vtbl을 거치지 않고 직접 호출하므로 본체가 꼭 있어야 한다. -> 정의를 해야 한다.
- 소멸자 동작 순서 : 가장 말단의 파생 클래스 -> 기본 클래스 쪽으로 올라가며 호출
- 다형성을 가진 기본 클래스에만 적용되는 사실.
- 모든 기본 클래스가 다형성을 갖도록 설계되진 않는다.
항목 8 : 예외가 소멸자를 떠나지 못하도록 붙들어 놓자.
-
C++은 예외를 내보내는 소멸자를 좋아하지 않는다.
- 예외를 소멸자가 던지지 않게 하는 두 가지 방법
- 예외 발생 시 프로그램 종료 : 에러 발생 후 프로그램 실행을 계속할 수 없는 상황
- 예외를 삼켜버린다 : 발생한 예외를 무시해도 프로그램이 신뢰성 있게 실행 지속되어야 한다.
- catch에서 어떤 처리도 해주지 않는다.
- 소멸자에서 예외 발생하는 부분을 “사용자가 제어할 수도 있게” 함수를 제공해준다.
ex)
>+ 예외를 처리할 필요가 있으면 그 예외는 소멸자가 아닌 다른 함수에서 비롯되어야 한다.