블로그 이미지
Kanais
Researcher & Developer 퍼즐을 완성하려면 퍼즐 조각들을 하나 둘씩 맞춰나가야 한다. 인생의 퍼즐 조각들을 하나 둘씩 맞춰나가다 보면 인생이란 퍼즐도 완성되는 날이 오려나...?

calendar

1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Notice

05-02 02:59

Recent Post

Recent Comment

Recent Trackback

Archive

2015. 4. 16. 11:43 Programming/C/C++

참고 서적 : Effective C++ - Meyers / 곽용재 옮김 / 피어슨에듀케이션코리아

 

2. 생성자, 소멸자 및 대입 연산자

생성자는 새로운 객체를 메모리에 만드는 데 필요한 과정을 제어하고 객체의 초기화를 맡는 함수이고, 소멸자는 객체를 없앰과 동시에 그 객체가 메모리에서 적절히 사라질 수 있도록 하는 과정을 제어하는 함수이며, 대입 연산자는 기존의 객체에 다른 객체의 값을 줄 때 사용하는 함수입니다. 잘 만들어진 클래스라면 반드시 갖고 있게 되는 대표적인 이 함수들을 어떻게 하면 멋지게 모아둘 수 있을까요? 그 ‘어떻게 하면’이 바로 이번 장에서 다루고자 하는 것입니다.



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

컴파일러가 생성하는 함수는 모두 public 멤버가 됩니다. 복사 생성자와 복사 대입 연산자가 저절로 만들어지는 것을 막기 위해 여러분이 직접 선언해야 한다는 점은 맞지만, 이것들을 public 멤버로 선언해야 한다고 요구하는 곳은 아무 데도 없습니다. 그러니까 public 멤버로 두지 말고, 복사 생성자 및 복사 대입 연산자를 private 멤버로 선언하도록 합시다. 일단 클래스 멤버 함수가 명시적으로 선언되기 때문에, 컴파일러는 자신의 기본 버전을 만들 수 없게 되지요. 게다가 이 함수들이 비공개(private)의 접근성을 가지므로, 외부로부터의 호출을 차단할 수 있습니다.

여기까지 90점입니다. 10점이 모자라죠. private 멤버 함수는 그 클래스의 멤버 함수 및 프렌드(friend) 함수가 호출할 수 있다는 점이 여전히 허점입니다. 이것까지 막으려면, 그러니까 ‘정의(define)’를 안해 버리는 기지를 발휘해 보면 어떨까요? 정의되지 않은 함수를 누군가가 어쩌다가 실수로 호출하려 했다면 분명히 링크 시점에 에러를 보게 될 테니 괜찮습니다. 실제로 이 꼼수[멤버 함수를 private 멤버로 선언하고 일부러 정의(구현)하지 않는 방법]는 꽤 널리 퍼지면서 하나의 ‘기법’으로 굳어지기까지 했습니다. C++의 iostream 라이브러리에 속한 몇몇 클래스에서도 복사 방지책으로 쓰이고 있지요. 이 꼼수를 HomeForSale에 사용해 봅시다.

class HomeForSale

{

public:

       ...

private:

       ...

       HomeForSale(const HomeForSale&); // 선언만달랑있습니다.

       HomeForSale& operator=(const HomeForSale&);

};

매개변수의 이름이 빠져 있는 게 살짝 거슬릴 수도 있겠습니다만, 선언 시 매개변수 이름은 필수사항이 아닙니다. 그냥 읽기 편하라고 해 주는 관례일 뿐이죠.

HomeForSale 클래스는 이렇게 정의되었습니다. 사용자가 HomeForSale 객체의 복사를 시도하려고 하면 컴파일러가 강한 백태클을 걸 것이고, 여러분이 깜박하고 멤버 함수 혹은 프렌드 함수 안에서 그렇게 하면 링커가 여러분을 싫어할 것입니다.

  한 가지 더 덧붙이면, 링크 시점 에러를 컴파일 시점 에러로 옮길 수도 있습니다. 복사 생성자와 복사 대입 연산자를 private로 선언하되, 이것을 HomeForSale 자체에 넣지 말고 별도의 기본 클래스에 넣고 이것으로부터 HomeForSale을 파생시키는 것입니다. 그리고 그 별도의 기본 클래스는 복사 방지만 맡는다는 특별한 의미를 부여합니다. 이 기본 클래스는 사실 단순 그 자체입니다.

class Uncopyable

{

protected:          // 파생된객체에대해서

       Uncopyable(){}      // 생성과

       ~Uncopyable(){}     // 소멸을허용합니다.

private:

       Uncopyable(const Uncopyable&);          // 하지만복사는방지합니다.

       Uncopyable& operator=(const Uncopyable&);

};

복사를 막고 싶은 HomeForSale 객체는 이제 이렇게 바꿔 봅시다 Uncopyable로부터 상속받게 하고 그냥 내버려 두는 것으로 끝입니다.

class HomeForSale: private Uncopyable

{                   // 복사생성자도,

       ...          // 복사대입연산자도

}                   // 이제는선언되지않습니다.

원하는 바를 깔끔하게 이루어 주는 코드입니다. HomeForSale 객체의 복사를 외부(멤버 함수나 프렌드 함수까지도)에서 시도하려고 할 때 컴파일러는 HomeForSale 클래스만의 복사 생성자와 복사 대입 연산자를 만들려고 할 것입니다. 컴파일러가 생성한 복사 함수는 기본 클래스의 대응 버전을 호출하게 되어 있습니다. 그런데 이런 호출은 지금 통하지 않게 됩니다. 아시다시피 복사 함수들이 기본 클래스에서 공개되어 있지 않기 때문입니다.

  Uncopyable로부터의 상속은 public일 필요가 없습니다. 그리고 Uncopyable의 소멸자는 가상 소멸자가 아니어도 됩니다. 또한 Uncopyable 클래스는 데이터 멤버가 전혀 없기 때문에 공백 기본 클래스 최적화(empty base class optimization) 기법이 먹혀 들어갈 여지도 없는데요. 하지만 Uncopyable 클래스는 기본 클래스이기 때문에 이 기법을 사용하면 다중 상속으로 갈 가능성이 있습니다.

 

이것만은 잊지 말자!

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

posted by Kanais