본문 바로가기
컴퓨터/Modern Effective C++ 정리

Modern Effective C++ Item 11 : Private 봉인술 <<< Delete 봉인술

by 김짱쌤 2015. 3. 24.

Item 11 : Private 봉인술 <<< Delete 봉인술

특정 함수의 호출을 금지하고 싶은 경우가 있다. 일반 함수는 그냥 선언을 하지 않으면 그만이다. 하지만 선언하지 않으면 c++님이 자동적으로 생성해주는 함수들이 있다. effective c++의 챕터2 item 6를 떠올려보자. 우리는 클래스의 복사생성자와 대입연산자가 자동으로 생성되는 것을 막기 위하여, private로 선언하고 정의하지 않음으로 어느곳에서도 복사생성자를 호출시키지 않게 만들었다.

class Widget
{
...
private:
   Widget(const Widget& rhs);            //복사 생성자 봉인
   Widget& operator=(const Widget& rhs); //복사 대입연산자 봉인
};

private로 선언하면, friends나 자기자신이 아닌이상 호출이 불가능하고, friends나 자기자신이어도 정의하지 않았기 때문에 링크과정에서 에러를 낸다.

C++11의 새로운 해결책 delete 선언

위의 방식은 C++98의 방법이다. C++11부터는 함수 뒤에 = delete라고 선언하는 것으로 해당 함수를 사용하지 않는다고 선언할 수 있다.

class Widget
{
...
public:
   Widget(const Widget& rhs) = delete;            //복사 생성자 봉인
   Widget& operator=(const Widget& rhs) = delete; //복사 대입연산자 봉인
};

좀더 깔끔하고 알아보기 쉬운 방식으로 함수를 봉인할 수 있다. 하지만 private봉인법과 delete 봉인법에는 그것보다 더 근본적인 차이점이 있다. 접근이 뚫린경우, 정의하지 않음으로 호출을 금지시키는 이전 private봉인법은 그것이 금지된 함수라는 걸 알아차리는 순간이 링크시점이었다. 하지만 이제 delete된 함수라는 것을 컴파일러가 미리 알아채기 때문에, 컴파일 타임에 봉인 함수를 체크할 수 있게 되었다. 이것은 delete봉인술이 우월한 분명한 이유가 된다.

delete 선언은 public에 사용하는 것이 좋다

delete된 함수를 호출하면 컴파일러가 "삭제된 함수를 참조하려고 합니다."라고 오류메시지를 띄워준다. 하지만 delete함수를 private에 선언한 경우, 접근권한 에러가 뜰 가능성이있다. 실제로는 delete되었기 때문에 사용하지 못한 것인데, private로 선언했기 때문에 제대로된 오류메시지를 받을 수 없는 것이다. 물론 private버전에서는 접근권한 오류메시지로 delete오류를 대신했겠지만, delete했다는 제약이 분명히 들어가는것은 삭제된 함수를 참조하려고 한다는 에러 메시지이므로, public으로 선언하는 것을 추천한다.

delete는 멤버함수가 아니어도 봉인하는 것이 가능하다.

private봉인술이 멤버함수로 그 사용폭이 제한되는 반면, delete는 일반 함수에도 적용시킬 수 있다.

bool isLucky(int number);

다른 타입의 인자를 받는 것을 제한하고 싶다면 delete 선언을 활용하여 위 함수를 원하지 않는 인자를 받는 함수를 오버로딩하여 선언하는 것이다.

bool isLucky(char) = delete;
bool isLucky(bool) = delete;
bool isLucky(double) = delete;

위의 선언중 float이 없는 이유 : c++에는 형변환 우선이라는 개념이 존재한다. float이 인자로 들어가면 사용할 수 있는 여러 함수중에 int보다는 double을 선택한다. 이유는 int보다 double이 float에 더 우선적으로 형변환되는 타입이기 때문이다. 따라서 bool isLucky(double) = delete;만 선언하는 것으로 float타입이 오는 것까지 봉인할 수 있다.

템플릿 함수에도 템플릿 특수화를 통한 delete봉인을 사용할 수 있다.

오버로딩하여 특정 인자를 막았던 위의 일반함수 delete처럼 특정 타입에 대하여 템플릿 특수화와 delete를 사용하여 함수가 호출되는 것을 막을 수 있다.

template<typename T>
void processPointer(T* ptr); //임의의 타입 포인터를 가지고 뭔가를 하는 함수

위 템플릿 함수의 경우, 역참조가 불가능한 void* 혹은 문자열을 표현하기 위한 char*들은 원치않는 손님이다. 따라서 이 예외들을 템플릿 특수화와 delete를 통해 봉인시키는 것이 가능하다.

template<>
void processPointer<void>(void*) = delete;

template<>
void processPointer<char>(char*) = delete;

좀더 엄밀하게 한다면 const void, const char, volatile void*...기타등등도 봉인을 해야겠지만, 결국 방법은 모두 위와같다.

클래스의 템플릿 멤버함수의 인자를 제한하는데에도 delete를 사용할 수 있다.

템플릿 멤버함수에 원치 않는 인자가 오는것을 방지하고싶다. 그러면 해당 인자를 집어넣은 템플릿 특수화를 통해 오버로딩하고 그것을 봉인하는 방법을 생각할 수 있다. C++98 이었으면 오버로딩한 함수를 private하는 방법을 생각해볼 수 있다.

class Widget
{
public:
   template<typename T>
   void processPointer(T* ptr)
   {...}
private:
   template<>
   void processPointer<void>(void*);  //error!
}

위의 에러의 이유는 두 가지가 있다.
  1. 멤버함수의 템플릿 특수화 버전은 기존 멤버함수와 다른 access level에 속할 수 없다.
  2. 템플릿 특수화는 클래스 영역이 아니라 반드시 네임스페이스 영역에서 쓰여져야한다.
따라서 위의 경우 private 봉인술로는 원하는 결과를 얻을 수 없다. 하지만 delete는 private일 필요도, class내부에 선언될 필요도 없다.

class Widget
{
public:
   template<typename T>
   void processPointer(T* ptr)
   {...}
}
template<>
void Widget::processPointer<void>(void*) = delete; //문제없이 봉인

delete봉인술은 private봉인술의 완벽한 상위호환이다. class영역 밖에서도 사용할 수 있으며, 에러시점이 컴파일 시점으로 고정되며, 에러메시지 또한 분명하다. 그러니까 우리모두 delete봉인술을 사용하자.



책의 내용과 다른 부분


템플릿 멤버함수에 대한 내용이 헷갈려, 실제 비주얼 스튜디오 2013환경에서 실혐해 보았다. 실제 결과는 위의 내용과 달리 나왔다. 특수화된 템플릿은 클래스 내부에서 잘 선언되었다. 뿐만아니라 접근 영역이 다른 템플릿 함수가 오버로딩 되었고, 네임스페이스 영역에서 선언한 경우 오히려 링킹에러가 발생하였다. 이유는???