- 컴파일러에서 자동으로 제공하는 기능을 허용하지 않으려면 대응되는 멤버 함수를 private로 선언한 후 구현하지 않은 채로 두면 된다. Uncopyable과 비슷한 기본 클래스를 사용하는 것도 좋다
컴파일러가 만든 함수 관리
컴파일러는 생성자, 소멸자, 복사 생성자, 복사 대입 연산자 등을 필요시에 만들어 낸다.
하지만, 때에 따라 이러한 것들이 허용되지 않게 막고 싶을 수 있다.
class HomeForSale { ... }; // 부동산 객체
HomeForSale h1;
HomeForSale h2;
HomeForSale h3(h1); // h1 복사 시도
h1 = h2; // h2 복사 시도
모든 자산은 세상에 단 하나밖에 없다.
하지만, 위의 코드에서는 복사하려는 시도를 하고 있다. 이러한 시도 자체를 금하고 싶을 때는 몇 가지 방법이 있다.
복사 생성자와 복사 대입 연산자는 만약 프로그래머가 선언하지 않았다면 컴파일러가 스스로 만들어낸다.
그렇기 때문에, 이것들을 명시적으로 막아두지 않는다면 복사를 허용하게 된다.
또한, 컴파일러가 생성하는 함수는 모두 public 멤버가 된다.
이러한 사실을 이용하면 복사를 막을 수 있다.
"복사 생성자와 복사 대입 연산자를 private 멤버로 명시적으로 선언한다."
이렇게 작성하면 클래스 멤버 함수가 명시적으로 선언되기 때문에 컴파일러는 자동으로 생성하지 못하고 private이기 때문에 접근도 불가능하다.
하지만, private 멤버 함수는 그 클래스의 멤버 함수 및 프렌드 함수가 호출할 수 있다.
이것까지 막으려면 정의를 안하면 된다.
정의되지 않은 함수를 사용하려 한다면 링크 시점에서 에러가 발생하기 때문에 복사를 막을 수 있다.
class HomeForSale {
public:
...
private:
...
HomeForSale(const HomeForSale&); // 선언만
HomeForSale& operator=(const HomeForSale&);
};
(매개변수의 이름이 없지만 구현하지 않을 것이라 상관없다)
Uncopyable
정의가 안된 멤버 함수를 사용하는 시도를 할 때 링크 시점에 에러가 발생한다.
이를 컴파일 시점으로 에러를 옮기 수 있다.
복사 생성자와 복사 대입 연산자를 private으로 선언하되, 이것을 HomeForSale 자체에 넣지 말고 복사를 방지하는 별도의 기본 클래스에 넣고 이것으로부터 HomeForSale을 파생시키는 것이다.
class Uncopyable {
protected:
Uncopyable() {} // 생성, 소멸 허용
~Uncopyable() {}
private:
Uncopyable(const Uncopyable&); // 복사 방지
Uncopyable& operator=(const Uncopyable&);
};
Uncopyable이라는 기본 클래스를 만들고 이를 상속하여 필요한 클래스를 만들면 자연스레 복사를 방지할 수 있다.
class HomeForSale: private Uncopyable {
...
};
컴파일러가 생성한 복사 함수는 기본 클래스의 대응 버전을 호출하게 된다.
기본 클래스에서 복사 함수를 공개하지 않았기 때문에 복사를 방지할 수 있다.
Uncopyable의 구현과 사용법 몇 가지
- Uncopyable로부터 상속은 public일 필요가 없다.
- Uncopyable의 소멸자는 가상 소멸자가 아니어도 된다.
- 데이터 멤버가 없으면 공백 기본 클래스 최적화 기법을 적용할 수 있지만 다중 상속에 대해 문제가 발생할 수 있다.
- 부스트 라이브러리를 보면 Uncopyable과 똑같은 기능을 하는 noncopyable이라는 클래스가 있다.