Chapter 2. 생성자, 소멸자 및 대입 연사자
항목 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자
- 경우에 따라 클레스에 대해 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자 를 암시적으로 만들어 놓을 수 있다.
- 이들은 모두
public
멤버이며 inline
함수입니다.
항목 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자
- 컴파일러에서 자동으로 제공하는 기능을 허용치 않으려면, 대응되는 멤버 함수를
private
로 선언한 후에 구현은 하지 않은 채로 둔다.
1 2 3 4 5 6 7 8
| class HomeForSale { public: ... private: ... HomeForSale(const HomeForSale&); };
|
- 대응되는 함수를
private
으로 선언하되, 이것을 별도의 기본 클래스에 넣고 이것으로부터 파생시킨다.
링크 에러전에 컴파일러 에러로 발견할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12
| class Uncopyable { protected: Uncopyable() {} ~Uncopyable() {} private: Uncopyable(const Uncopyable&); Uncopyable& operator=(const Uncopyable&); };
class HomeForSale : private Uncopyable{ ... };
|
항목 7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자.
- 기본 클래스 포인터 를 통해 파생 클래스 객채가 삭제될 떄 그 기본 클래스에 비가상 소멸자가 들어 있으면 프로그램 동작은 미정의 사항이다.
- 따라서 가상 함수를 하나라도 가진 클래스는 가상 소멸자를 가져야 하는게 대부분 맞다.
- 기본 클래스로 설계되지 않았거나 다형성을 갖도록 설계되지 않은 클래스에는 가상 소멸자를 선언하지 말아야 합니다.
- 순수 가상 소멸자 활용 : 추상 클래스였으면 좋겠는데 마땅히 넣을 만한 순수 가상 함수가 없을 때 사용
1 2 3 4 5 6 7 8
| class AMOV { public: virtual ~AMOV() = 0; };
AMOV::~AMOV() {}
|
항목 8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자
- 소멸자에서는 예외가 빠져나가면 안 됩니다.
- 소멸자 안에서 호출된 함수가 예외를 던질 가능성이 있다면, 어떤 예외이던지 소멸자에서 모두 받아낸 후에 삼켜 버리든지 프로그램을 끝내든지 해야 합니다.
1 2 3 4 5 6 7 8 9
| DBConn::~DBConn() { try {db.close();} catch(...) { std::abort(); } }
|
발생한 예외를 그냥 무시한 뒤라도 프로그램이 신뢰성 있게 실행을 지속할 수 있어야 한다.
1 2 3 4 5 6 7 8
| DBConn::~DBConn() { try {db.close();} catch(...) { } }
|
- 예외에 대해 사용자가 반응해야 할 필요가 있다면 해당 연산을 제공하는 함수는 반드시 보통의 함수(소멸자가 아닌 함수)이어야 합니다.
예외는 소멸자가 아닌 다른 함수에서 비롯된 것이어야 한다. > 사용자에게 에러를 처리할 수 잇는 기회를 주는 것입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class DBConn { public: ... void close() { db.close(); closed = true; } ~DBConn() { if(!closed) try { db.close(); } catch(...) { ... } } private: DBConnection db; bool closed; };
|
항목 9. 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자
- 생성자 혹은 소멸자 안에서 가상 함수를 호출하지 마세요. 가상 함수라고 해도 지금 실행중인 생성자나 소멸자에 해당되는 클래스의 파생 클래스 쪽으로는 내려가지 않으니까요.
- 순수 가상 함수의 경우 에러가 발생합니다.