예를 들어 사본을 만들어 내는 것을 금지한다고 생각해보자.
사본을 만들어내는 방법은 간단하게 복사 생성자 혹은 복사 대입 연산자가 있을 것이다.
Empty e1;
Empty e2;
Empty e3(e1); //복사 생성자
e2 = e1; //복사 대입 연산자
일반적으로 구현하지 않으면 그만이지만, 컴파일러는 생성자, 소멸자, 복사 생성자, 복사 대입 연산자를 자동으로 만든다(5장 참고).
컴파일러가 자동으로 생성하는 것들은 public 멤버로 생성되는데, 반대로 생각해 보면 private으로 개발자가 구현하면 외부로 부터 호출을 차단할 수 있고, 컴파일러가 자동으로 생성하지 않으니 막을 방법이 될 수 있다.
하지만 private으로 구현한다는 것은 한 가지 오점을 갖고 있다. private 멤버 함수는 firend함수가 호출할 수 있다는 것이다. friend함수 까지 막으려면 선언만 해놓고 구현을 안한다는 꼼수가 있다.
class Empty
{
private:
Empty(const Empty&); //혹여 호출 되더라도 아무런 일이 일어나지 않는다.
Empty& operator=(const Empty&); //마찬가지
};
선언만 하는 것이라면 매개변수의 이름은 쓰지 않아도 상관 없다.
C++의 iostream라이브러리에 속한 몇몇 클래스에서도 복사 방지책으로 쓰이고 있는 방법이다.
나중에 ios_base, basic_ios, sentry가 코드를 들여다보자. 복사 생성자와 복사 대입 연산자 모두 private 멤버로 선언된 동시에 정의되어 있지도 않다고 한다.
위의 방법은 인텔리센스 혹은 링크가 에러를 감지하지는 못하고 빌드가 되어야 에러라는 것을 알 수 있다.
에러는 빨리 알수록 좋으니 가시적인 에러 링크가 나오도록 하는 방법이 있다.
class Uncopyable
{
protected:
Uncopyable() {} //생성자 호출 허용
~Uncopyable() {} //소멸자 호출 허용
private:
Uncopyable(const Uncopyable&); //복사 생성자 호출 불가
Uncopyable& operator=(const Uncopyable&); //복사 대입 연산자 호출 불가
};
class HomeForSale : private Uncopyable //Uncopyable 상속
{
};
int main()
{
HomeForSale hs1; //Uncopyable의 생성자 사용
HomeForSale hs2; //Uncopyable의 생성자 사용
hs2 = hs1; //에러
}
복사 생성자와 복사 대입 연산자를 private으로 선언하되, 이것을 HomeForSale에 넣지 않고 Uncopyable 클래스에서 파생시키는 방법이다. Uncopyable클래스는 복사 방지만 맡는다. 만약 HomeForSale에서 생성자를 구현한다면 상속개념에 따라 HomeForSale의 생성자를 사용할 것이다.
마지막으로 Uncopyable의 구현 방법과 기술적인 미묘한 부분이 있다.
Uncopyable로 부터의 상속은 public일 필요가 없다(32장, 39장 참고).
Uncopyable의 소멸자는 가상 소멸자가 아니어도 된다(7장 참고).
Uncopyable클래스는 멤버 변수가 전혀 없기 때문에 기본 클래스 최적화(empty base class optimization)기법(39장 참고)이 가능한데, Uncopyable클래스는 기본 클래스이기 때문에 이 기법을 사용하면 다중 상속으로 갈 가능성이 있다(40장 참고).
부스트 라이브러리(55장 참고)를 보면 Uncopyable와 똑같은 구실을 하는 클래스가 있는데, 이것을 사용해도 된다.
부스트 라이브러리에서는 noncopyable이다.
이것만은 잊지 말자
컴파일러에서 자동으로 제공하는 기능을 허용하지 않으려면, 대응되는 멤버 함수를 private으로 선언한 뒤 구현하지 않은 채로 두자.
위의 Uncopyalbe과 비슷한 기본 클래스를 사용하는 것도 방법이다.
선언만 하는 것이라면 매개변수의 이름은 쓰지 않아도 상관 없다.
'서적 정리 > Effective C++' 카테고리의 다른 글
10.대입 연산자는 *this의 참조자를 반환하게 하자 (0) | 2021.12.10 |
---|---|
9.객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자 (0) | 2021.12.10 |
8.예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2021.12.10 |
7.다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 (0) | 2021.12.10 |
5.C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (0) | 2021.12.10 |
4.객체를 사용하기 전에 반드시 그 객체를 초기화하자 (0) | 2021.12.10 |
3.낌새만 보이면 const를 들이대 보자! (0) | 2021.12.10 |
2.#define을 쓰려거든 const, enum, inline을 떠올리자 (0) | 2021.12.10 |
댓글