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

Jangmanbo·2023년 2월 14일
0

Effective C++

목록 보기
6/33
class HomeForSale { ... };

부동산 중개업 소프트웨어에서 집을 나타내는 클래스인 HomeForSale을 선언한다.
그런데 부동산 중개업자가 세상에 똑같은 집은 존재하지 않는다고 신신당부하니 HomeForSale 객체를 복사하려는 코드는 컴파일되지 않도록 하고 싶다.

HomeForSale h1;
HomeForSale h2;

HomeForSale h3(h1);	// Error. 복사 생성자 호출 시도

h1 = h2;			// Error. 복사 대입 연산자 호출 시도

즉, 다음과 같이 동작하게 만들고 싶다.
하지만 C++은 사용자가 복사 생성자, 복사 대입 연산자를 생성하지 않는다면 컴파일러가 알아서 선언해 버린다. 참고: Effective C++ 항목5
따라서 사용자가 생성하지 않아도 위의 코드는 에러 없이 정상으로 작동할 것이다.

해결법

컴파일러가 암시적으로 생성하는 함수는 모두 public 멤버이다.
사용자가 private 멤버로 복사 생성자와 복사 대입 연산자를 선언한다면, private의 접근성을 가지므로 외부로부터의 호출을 차단할 수 있으며 컴파일러는 암시적으로 함수를 생성할 수 없다.

그러나 private 멤버 함수는 그 클래스의 멤버 함수 및 friend함수가 호출할 수 있다는 문제점이 여전히 존재한다. 이 문제까지 막으려면 private으로 선언만 하고 정의를 하지 않으면 된다. 실제로 이 기법은 C++의 iostream 라이브러리의 몇몇 클래스에서도 쓰이는 방법이다.

class HomeForSale {
public:
	...
private:
	...
    // 선언 O 정의 X
    // 매개변수의 이름은 필수가 아님 (구현하지 않으니 매개변수의 이름이 없어도 됨)
    HomeForSale(const HomeForSale&);
    HomeForSale& operator=(const HomeForSale&);
};

이렇게 정의는 안하고 선언만 달랑 한다면 사용자가 HomeForSale 객체의 복사를 시도할 때 컴파일러 에러가 발생하게 된다.

에러 발생 시점 앞당기기

앞에서 말한 방법은 링크 시점에 에러가 발생한다.
에러 탐지는 미리 하는 것이 좋으므로 링크가 아닌 컴파일 시점에 에러가 발생하도록 개선해보자.

class Uncopyable {
protected:	// 파생된 객체에 대해서
	// 생성, 소멸 허용
	Uncopyable();
    ~Uncopyable();
private:
	// 복사는 허용X
   Uncopyable(const Uncopable&);
   Uncopyable& operator=(const Uncopable&);
};
class HomeForSale: private Uncopyable {
	...
};

앞서 구현했던 것처럼 복사 생성자와 복사 대입 연산자를 private으로 선언하되, HomeForSale 클래스가 아닌 별도의 기본 클래스에서 선언하고 기본 클래스로부터 HomeForSale을 파생시킨다.
이때 기본 클래스인 Uncopyable은 복사 방지라는 역할만을 수행한다.

0개의 댓글