Item 6: 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금하자
사본을 만드는 것을 막아야 하는 경우를 가정해 보자.
class HomeForSale { ... };
HomeForSale h1;
HomeForSale h2;
HomeForSale h3(h1); // 이렇게 복사하면 안됨.
h1 = h2; // 이렇게도 복사하면 안됨.
위 코드에서 HomeForSale을 선언할 때 복사생성자나 복사대입연산자를 사용자가 직접 정의하지 않았다고 하더라도 컴파일러는 HomeForSale h3(h1)와 h1 = h2 와 같은 코드를 보고 자동으로 이들을 생성해 버린다.
컴파일러가 자동으로 이 함수들을 만드는 것을 막는 방법은 사용자가 이 함수들을 직접 만드는 것이다. 또한 클래스 밖에서 이들이 호출되지 않게 하기 위해 public 이 아닌 것으로 선언하는 것이다.
이렇게 한다고 해도 다른 멤버함수에서 호출이 가능한 상황이다. 이를 해결하는 방법으로는 선언만 하고 정의를 하지 않는 것이다.
해결 방법
- private으로 선언만하고
- 정의를 하지 말자.
class HomeForSale {
public:
...
private:
...
HomeForSale(const HomeForSale&); // 선언만 존재
HomeForSale& operaotr=(const HomeForSale&); // 마찬가지
위 경우는 에러가 링크 시점에 나온다. 하지만 에러는 뒤로 미룰수록 좋지 않다. 이를 해결해 보자.
해결 방법
- 복사를 방지하는 전담 클래스를 만들자.
class Uncopyalble {
protected: // 파생 클래스에 대해
Uncopyable() {} // 생성과 소멸을
~Uncopyable() {} // 허용한다
private:
Uncopyable(const Uncopyable&); // 하지만 복사는
Uncopyable& operator(const Uncopyable&); // 허용하지 않는다.
복사를 막고 싶은 객체는 이렇게 사용하자.
class HomeForSale: private Uncopyable { // 복사생성자도,
... // 복사대입연산자도
}; // 이제는 선언되지 않는다.
만약 누군가가 이 객체를 복사하려고 하면 컴파일러는 복사생성자나 복사대입연산자를 자도으로 만들려고 시도할 것이고 이 과정에서 베이스클래스의 복사생성자와 복사대입연산자를 호출하려고 할 것이다. 이때 컴파일 에러가 발생한다.
- public 상속일 필요가 없다 (항목 32, 39).
- Uncopyable은 가상 소멸자가 아니어도 된다.(항목 7)
공백 기본클래스 최적화가 적용 가능하다.
다중 상속의 가능이 있다.(항목 40) ㅡㅡ;
- 다중 상속시 공백 기본클래스 최적화 적용이 안됨.
정리
- 컴파일러에서 자동으로 제공하는 기능을 허용치 않으러면, 대응되는 멤버함수를 private으로 선언한 후에 구현은 하지 말자.
- Uncopyable 클래스를 쓰는 것도 좋다.