Skip to content

item 11 요약 정리

ozt88 edited this page Mar 24, 2015 · 1 revision

#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에 사용하는 것이 convention

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봉인술을 사용하자.