항목 6 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자
항목 5에 대해서는 글을 쓰지 않았는데 대충 복사 생성자, 대입 연산자같은 함수들은 클래스 설계자가 인위적으로 만들지 않아도 필요에 따라 컴파일러가 만든다는 내용이었다. 이와 같이 컴파일러는 설계자가 만들지 않은 클래스중 필요하다고 생각 되는것은 자신이 직접 생성을 한다. 자, 그러면 여기서 나는 하나의 클래스를 설계하는데 절대로 복사, 대입이 일어나서는 안되는 클래스를 설계한다고 치자. 나는 그래서 일부러 클래스에 복사생성자, 대입연산자를 만들지 않았다. 그런다고 정말 복사가 안될까? 아니다 컴파일러가 직접 생성을 해버린다.
class Home { ... };
Home h1;
Home h2;
Home h3(h1); // 오류가 나기를 바람
h1 = h2; // 오류가 나기를 바람
나는 이렇게 했을 때 오류가 나길 바랬지만 컴파일러는 이 코드를 보고 복사생성자와 대입 연산자를 만들어 버린다. 그렇다면 이것을 어떻게 막을 수 있을까? 방법은 간단하다. 복사생성자와 대입 연산자를 private으로 정의하는 것이다.
class Home
{
private:
Home(const Home&);
Home& operator=(const Home&);
};
Home h1;
Home h2;
Home h3(h1); // 오류
h1 = h2; // 오류
자 위의 코드에서 보면 함수에 대한 정의만 하고 선언은 하지 않았다. 만약 선언을 한다면 클래스의 멤버 함수나 friend함수에서 여전히 복사 및 대입 연산자를 호출 할 수 있게 되버리기 때문이다. 선언을 하지 않으면 컴파일러가 호출하려는 함수의 선언부를 찾지 못해 오류가 발생되어 막을 수 있게 된다. 자 하지만 이 오류는 링커오류이다. 링커오류 전 컴파일 시점에서 더 일찍 오류를 발생시키는 방법도 있다. 그것은 바로 별도의 클래스를 만들어 파생 시키는 것이다.
class Uncopyable
{
protected:
Uncopyable();
~Uncopyable();
private:
Uncopyable(const Uncopyable&);
Uncopyable operator=(const Uncopyable&);
};
class Home : private Uncopyable {
...
};
Home h1;
Home h2;
Home h3(h1); // 오류
h1 = h2; // 오류
이런 식으로 복사 및 대입이 불가능한 클래스를 만들어 상속시키면 컴파일 단계에서 오류가 날 수 있도록 만들 수 있다. 그리고 더 깔끔하기까지 하다. 또한 Uncopyable의 생성자는 protected에 선언하여 별도로는 생성할 수 없도록 하는 것도 좋다. 더 나아가 boost 라이브러리의 noncopyable클래스가 이와 비슷한 역할을 하는데 이것은 최적화까지 되어있어서 더 좋다. 나중에 한 번 써봐야겠다.