본문 바로가기

Effective C++

[EC++] 항목 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자

 이 항목은 이전 항목 5와 연장선상에 있는 항목이라고 할 수 있는데요, 이 세상에 복사가 불가능한 유일한 문서가 있다고 가정해 봅시다. 그 문서를 이름이 Unique_Doc 이라고 해 봅시다. 이 Unique_Doc를 나타내는 클래스가 있다고 해봅시다.
 이 객체는 복사가 불가능하므로 이것의 사본(copy)을 만드는 것 자체가 이치에 맞지 않는다고 볼 수 있습니다. 그래서 Unique_Doc 객체를 복사하는 아래와 같은 코드는 컴파일 되지 않았으면 하는 생각을 가지게 됩니다. 
 하지만 아무리 주석으로 경고를 해놔도, 이것을 사용하지 않도록 100% 막을수는 없을것입니다. 제일 좋은 방법은 이런 복사 기능을 제공하는 함수를 선언하지 않는 것입니다. 하지만 항목 5에서도 살펴봤다시피 클래스의 복사 생성자와 복새 대입 연산자는 우리가 선언을 하지 않아도 외부에서 호출하려고 하면 컴파일러가 알아서 선언을 해버리기 때문에 이런 방법은 통하지 않습니다.  그럼 어떤 방법이 있을까?

 해결 방법  
 첫번째 방법은, 보통 우리가 쓰거나 컴파일러에 의해서 쓰는 복사 생성자와 복사 대입 연산자는 public 멤버에 선언하기 마련입니다. 이렇게 public 멤버에 선언한 이것들을 private으로 선언하는 방법입니다. 일단 클래스 멤버 함수가 명시적으로 선언되기 때문에, 컴파일러는 자신의 기본 버전을 만들 수 없게 되고 이 함수들이 private의 접근성을 가지므로 외부로부터의 호출을 차단할 수 있는 것입니다. 

 하지만 여기에도 허점이 있는데, private 멤버 함수는 그 클래스의 멤버 함수 및 프렌드(friend) 함수가 호출 할 수 있다는 점입니다. 이점까지 막기 위해서는 복사 생성자와 대입 연산자를 private에 선언만 해놓고 구현은 하지 않는 방법을 쓸 수 있습니다. 이런 방법은 '기법'으로까지 굳어져 C++의 iostream 라이브러리에 속한 몇몇 클래스에서도 복사 방지책으로 쓰이고 있기 까지 합니다. (아래와 같이 사용 할 수 있겠죠)
 이렇게 선언된 클래스에 사용자가 Unique_Doc 객체의 복사를 시도하려고 하면 링크 시점에서 에러를 보일것입니다. 이것을 프로그래머가 더 보기 편하게 컴파일단으로 이 에러를 옮길 수도 있습니다. 그 방법은 복사 생성자와 복사 대입연산자를 private로 선언하되, 이것을 해당 클래스에 놓지 말고 별도의 클래스에 넣고 이 기본 클래스로 부터 Unique_Doc 클래스를 파생시키는 것입니다. (상속을 이용) 아래와 같이 구현 될 수 있겠습니다.
빌드 해보면, 컴파일러 단에서 에러가 나오는것을 보실 수 있습니다. (이런 클래스는 boost에서도 제공하는 것이 있다고 합니다.)


이 UnCopyable 클래스의 장점을 두가지를 뽑을 수 있습니다.
 - 상속 받은 클래스에서는 복사 생성자와 대입연산자를 선언할 필요가 없다.
 -  클래스에서 직접 선언을 하면 어떤 상황에 쓰이냐에 따라서 컴파일 에러가 날 수도 있고, 링크 에러가 날 수도 있는데, 링크 에러 같은 경우 컴파일 에러보다 더 잡기 어렵죠. 하지만 상속 받아서 처리하면 항상 컴파일 에러가 납니다. 
  그 이유는 프로그래머가 상속받은 클래스의 복사 생성자와 대입연산자를 선언/구현하지 않고 해당하는 일을 하려고 할 때 컴파일러에서 자동으로 만들어주는 복사 생성자와 대입연산자를 사용하는데, 이 경우에는 부모 클래스의 복사 생성자와 대입연산자를 자동으로 불러주기 때문입니다. 

컴파일러에서 자동으로 제공하는 기능을 허용치 않으려면, 대응되는 멤버 함수를 private로 선언한 후에 구현은 하지 않은 채로 두십시오. UnCopyable과 비슷한 기본 클래스를 쓰는것도 한 방법입니다.