Item 5. C++가 은근슬쩍 만들어서 호출하는 함수에 대해 촉각을 세우자.

복사 생성자, 복사 대입 생성자, 기본생성, 소멸자을 기본으로 만들고 전부 public 접근 지정자이며 inline 함수이다.

기본 생성과 소멸자인경우, 컴파일러에게 코드를 깔수 있는 자리를 마련해준다.
만약 상속관계에서의 소멸자인경우, 상속한 클래스가 가상소멸자 선언을 안되어 있다면 비가상 소멸자로 만들어진다.
복사생성자와 복사 대입생성자가 기본으로 생성되는 경우에는, 원본 객체의 비정적 데이터를 사본으로 그저 복사할뿐이다. (얇은 복사)

결정적으로 복사 대입 연산자를 private로 선언한 기본 클래스로부터 파생된 클래스인 경우, 암시적 복사 대입 연산자를 가질수 없다.

파생 클래스에 대해 컴파일러가 만들어주는 복사 대입 연산자는 기본 클래스 부분을 맡도록 되어 있도록 되어 있긴하지만, 이렇게 하더라도 파생클래스에서 호출할 권한이 없는 맴버 함수는 암시적 복사 대입 연산자가 어떻게 호출할수 없다.

컴파일러가 파생클래스의 생성한 복사 함수(복사생성, 복사 대입)는 기본 클래스의 대응 버전을 호출하도록 되어 있는다는 점!

Item 6. 컴파일러가 만들어낸 함수가 필요없다면 확실히 이들의 사용을 금해버리자.

컴파일러에서 자동으로 제공하는 기능을 허용치 않으려면, 대응되는 맴버 함수를 private로 선언한뒤 구현을 하지않은채로 두면된다.
또는 Uncopyable라는 클래스를 만들고 private 로 상속시키는 것도 방법

Item 7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자.

문제는 포인터가 가르키는 객체의 타입에 맞춰서 삭제된다는 점인데, 비가상 소멸자가 들어 있다면 파생 클래스 객체가 삭제될때,
프로그램의 동작은 미정의 사항이라고 되어 있다. 대게 그 객체의 파생 클래스 부분이 소멸되지 않게 된다. // 소멸자를 호출할때, 기준의 포인터 형태가 중요

가상 소멸자를 선언하는 것은 그 클래스에 가상함수가 하나라도 들어있는 경우에만 한정하자.

기본클래스로 설계되지 않았거나 다형성을 갖도록 설계되지않은 클래스에는 가상소멸자를 선언하지 말아야 됩니다. - 파생 클래스의 소멸자가 호출되지 않게됩니다.

Item 8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자.

만약 vector가 있을때, 벡터의 타입은 어떤 클래스의 인스턴스인 상황에서, 첫번째 것을 소멸시키는 도중에 예외가 발생하였고, 다음 나머지 객체들의 소멸을 위해 소멸자를 호출하고 이때 또 예외가 나온다면 활성화된 예외가 2개 이상인 상황이면, C++에서 감당하기 버거워진다.

걱정처리를 피하는 방법 1 프로그램을 바로 끝낸다.

1
2
3
4
5
6
7
8
try
{
~~~
} catch(...)
{
// close 로그 작성
std::abort();
}

걱정거리를 피하는 방법2. 예외를 삼겨버린다.

1
2
3
4
5
6
7
try
{
~~~
} catch(...)
{
// close 로그 작성
}

예외 삼키기는 그리 좋진 않다. 그저 로그만 알려줄뿐이다. 2번째 선택이 빛을 발하기 위해서는 예외를 무시하더라도 신뢰성있게 실행을 지속할수 있어야만 된다.
더 좋은 방법으로는, 예외가 소멸자가 아닌 다른 함수에서 비롯된 것이어야 된다. 소멸자는 프로그램의 불완전 종료 혹은 미정의 동작의 위험을 내포하고 있기 때문에
close함수를 따로만들어서 호출시킨다면, 사용자에게 에러를 처리할수 있는 기회를 주게된다.

Item 9. 객체 생성 및 소멸 과정에서는 가상함수를 호출하지 말자.

호출결과가 원하는대로 돌아가지 않기 때문.
상속관계에서의 생성자 호출은 기본 클래스의 생성자 다음에 파생 클래스의 생성자가 호출되는데, 기본 클래스 생성자가 돌아가고 있을때 어쩌다가 가상함수를 호출하게되고,
파생 클래스 쪽을 건들이게 된다면, 아직 파생클래스는 초기화되지 않은 상황이라 어떻게 될지 모르는 상황이 온다.

파생 클래스 객체의 기본 클래스 부분이 생성되는 동안, 그 객체의 타입은 바로 기본 클래스.
호출되는 가상 함수는 모두 기본 클래스의 것으로 resolve(결정)될 뿐만 아니라, 런타임 타입 정보를 사용하는 언어 요소를 사용한다고 해도,
이순간에는 기본 클래스 타입으로 취급된다.

Item 10. 대입 연산자는 *this 참조자를 반환하게 하자.

1
2
3
4
5
6
7
8
class Widget
{
public:
Widget& operator=(const Widget& rhs)
{
return *this;
}
}

Item 11. operator= 에서는 자기대입에 대한 처리가 빠지지 않도록 하자.

1
2
3
4
5
6
Widget& operator=(const Widget& rhs)
{
if(this == &rhs)
return *this;
return *this;
}

수많이 대입이 일어난다면, 처리가 느려진다. 이때의 기법이 Copy and Swap.

1
2
3
4
5
6
7
Widget& operator=(const Widget& rhs)
{
Widget Temp(rhs); // rhs에 대해 사본 -> 복사생성자
swap(temp); // *this와 temp을 맞바꾼다.

return *this;
}

두 개 이상의 객체 대해 동작하는 함수가 있다면 이 함수에 넘겨지는 객체들이 사실
같은 객체인지 확인!

Item 12. 객체의 모든 부분을 빠짐없이 복사하자.

객체 복사 함수는 주어진 객체의 모든 데이터 맴버 및 모든 기본 클래스 부분을 빠뜨리지 말고 복사한다.

클래스의 복사 함수 2개 를 구현할때, 한쪽을 이용해서 다른쪽을 구현하려고 하지마라.
그 대신 공통된 동작을 제 3의 함수에 분리해 놓고 양쪽에서 호출해서 해결.