2017년 3월 9일 목요일

C++ deleted functions (함수의 삭제 선언)

 C++11에 새롭게 추가된 함수의 삭제(delete)에 대해서 정리해보려고 한다. 어떤 함수를 삭제(delete)시키면 그 함수에 대한 어떤 호출도 불가능하게 된다. 물론 함수를 사용하지 못하게 할 거라면 그냥 함수를 작성하지 않으면 된다. 하지만 문제가 발생하는 함수들은 컴파일러가 자동으로 생성해주는 함수들이다. C++ 컴파일러는 클래스의 생성자, 소멸자, 대입연산자 등을 필요에 의하여 자동으로 생성해주는 기능이 있다. 하지만 개발자는 일부러 그런 함수들을 사용하지 못하게 하려고 만들지 않았는데 컴파일러가 알아서 생성해 준다면 꽤 귀찮은 문제가 된다. 이럴 때 사용하는 방법이 함수를 delete시키는 것이다.

 C++11 이전에는 그런 메소드를 private으로 선언함으로써 다른 개발자들이 사용할 수 없도록 하였으나 C++11에서는 아래 예와 같이 함수를 delete시키는 방법을 제공해 준다.
class NoCopyClass {
public:
    ...
    NoCopyClass(const NoCopyClass&) = delete;
    NoCopyClass& operator=(const NoCopyClass&) = delete;
    ...
};
 복사 생성자와 복사 대입 연산자를 삭제함으로써 클래스의 객체를 복사할 수 없게하려고 한다는 것을 짐작할 수 있다.(이렇게 의도가 전달되는 것은 좋은 일이다.) 이런 생성자와 연산자들은 컴파일러가 기본 구현을 제공하는 것들이지만 개발자가 직접 삭제 함수(deleted function)로 구현한 것이기 때문에 컴파일러도 기본 구현을 제공하지 않고 다른 개발자들이 호출할 수도 없게 되는 것이다.

 참고로 함수의 삭제 선언은 public으로 선언하는 것이 관례이다. 왜냐하면 컴파일러가 함수의 삭제여부 이전에 private여부를 먼저 판단하기 때문에 삭제된 함수에 대한 정확한 메시지를 출력하도록 유도하려면 public으로 선언해야하기 때문이다.

 함수의 삭제는 private으로 선언하는 방법보다 좋은 점이 몇 가지 있는데 클래스의 멤버 함수 외에 모든 함수가 삭제 가능하다는 점이다. 이러한 점을 이용해서 "함수 인자의 암묵적인 변환을 이용한 호출"을 막을 수도 있다. 아래 예에서 isLucky()함수는 정수형을 받는 함수지만 암묵적 변환이 가능한 인자들을 넣고 호출해도 모두 호출이 된다. 하지만 호출을 막고자 하는 타입을 받는 overload 함수를 delete로 선언해버리면 암묵적 변환 전에 완벽하게 매칭되는 함수가 호출되는 것이 우선이므로 삭제된 함수를 호출하게 되고 따라서 호출 불가 판정을 하게 되는 것이다.
bool isLucky(int number);
bool isLucky(double) = delete;

if (isLucky(7)) // 호출 가능
    ...
if (isLucky('a')) // 호출 가능
    ...
if (isLucky(3.5)) // 호출 불가능. 에러!
    ...
 double 타입 인자를 받는 isLucky() 함수가 선언되지 않았으면 3.5는 int형으로 변환되어 호출이 되었을 것이다. 하지만 double 인자 버전이 delete로 선언되었기 때문에 삭제된 함수에 매칭이 되어 호출이 되지 않고 에러를 발생시킨다. 알아두면 가끔 유용하게 쓰일 것 같다.

 함수의 삭제는 템플릿의 특정 특수화(specialization) 버전을 삭제함으로써 그 함수가 생성되는 것을 막는데 이용할 수도 있다. 함수 템플릿은 컴파일러가 추론된 타입에 맞춰서 그때그때 생성하게 되는데 특정 타입에 대한 함수를 생성하지 못하도록 하려고 할 때 미리 사용을 막고자 하는 타입의 특수화 버전을 delete로 선언해버리면 컴파일러는 특수화 함수가 존재하므로 코드를 생성하지 않고 삭제된 함수이므로 호출도 하지 못하게 되는 것이다. 아래 예를 보면 어떤 상황인지 어렵지 않게 이해할 수 있다.
template<typename T>
void processPointer(T* ptr);

template<>
void processPointer<void>(void*) = delete; // void 포인터는 호출 불가!
 void 타입의 특수화 함수를 선언하고 삭제함으로써 그런 함수가 생성되는 걸 막고 당연히 호출도 불가하게 만드는 것이다.

 위와 같이 함수의 delete 선언이 주는 장점은 여러가지가 있지만 사실 가장 큰 장점은 코드의 의도를 분명히 할 수 있다는 데 있다. 특정 메소드를 사용하지 못하게 하려고 private의 선언하는 것보다 delete로 선언하는 게 더 분명하게 의도를 전달할 것이고 그런 코드를 읽게 된다면 무엇을 의도하고 함수를 삭제했는지 파악하는 것도 private으로 선언하는 것보다 훨씬 쉬울 것이다. 코드를 읽기 쉽게, 의도를 분명하게 작성하는 건 기본 중의 기본이고 항상 바람직한 일이다.

댓글 3개:

  1. 참고로 함수의 삭제 선언은 public으로 선언하는 것이 관례이다. 왜냐하면 컴파일러가 함수의 삭제여부 이전에 private여부를 먼저 판단하기 때문에 삭제된 함수에 대한 정확한 메시지를 출력하도록 유도하려면 public으로 선언해야하기 때문이다.

    ==================

    혹시 이부분이 무슨뜻인가요??
    함수에 대한 정확한 메시지의 출력을 유도할 수 있다는게 무슨 말인지 모르겠네요

    답글삭제
    답글
    1. private로 함수를 삭제 선언을 구현한다.
      -> 컴파일러에서 순서상 이유로 접근제어 관련 오류를 뱉게 만들기 때문에 함수가 삭제되었다는 의도를 다른 개발자가 파악할 수 없는 이유로 인한 것이라 보입니다.

      삭제
    2. 오호라. 사용하지 못하는 건 똑같은데 컴파일러가 에러를 뱉을때 private인데 왜썼어?로 뱉냐, delete됐는데 왜썼어?로 뱉냐의 차이라는 거군요.
      --
      실제 돌려보니 그러네요.

      삭제