[EMC++] Item 11. 정의되지 않은 private function보다 deleted function을 선호해라
Item 11. 정의되지 않은 private function보다 deleted function을 선호해라
특정 함수를 사용하지 못하게 하는 방법은 무엇이 있을까?
당연한 이야기지만 특정 함수를 구현하지 않으면 사용할 수 없다.
하지만 C++에서는 내가 구현하지 않아도 알아서 만들어지는 함수들이 존재한다.
예를 들어 C++에서 클래스를 구현하면 내가 따로 선언해주지 않아도 컴파일러가 특수 멤버 함수를 만들어 준다.
C++98에서는 특수 멤버 함수로 기본 생성자, 소멸자, 복사 생성자, 복사 대입 연산자가 만들어지고 C++11에서는 이동 생성자, 이동 대입 연산자가 추가되었다.
이처럼 직접 구현하지 않아도 함수가 존재할 수 있으며 내가 원하지 않는 함수를 사용하지 못하게 하는 방법이 필요하다.
정의되지 않은 private function을 이용하는 방법
C++98에서는 특정 함수의 사용을 막기 위해 함수를 private
으로 선언하고 함수의 정의를 만들지 않는 방법을 이용했다.
대표적인 예시로 C++ 표준 입출력 라이브러리에 구현된 basic_ios
라는 클래스 템플릿이 있다.
입출력 스트림을 나타내는 해당 클래스는 복사 생성 및 대입 연산이 어떠한 행위를 해야 할지 불명확하여 정의되지 않은 private function을 이용하여 사용을 막았다.
template <class charT, class traits = char_traits<charT> )>
class basic_ios : public ios_base {
private:
basic_ios(const basic_ios&); // not defined
basic_ios& operator=(const basic_ios&); // not defined
private
으로 생성한 함수는 외부에서 호출할 수 없다. 또한, 멤버 함수나 friend
함수가 private
함수를 호출해도 정의가 없어서 링크에 실패한다.
존재하지만 사용할 수 없는 함수로 만들어 버린 것이다.
deleted function을 이용하는 방법
C++11에서는 함수 선언의 끝에 = delete
키워드를 붙여서 사용을 막을 수 있다.
template <class charT, class traits = char_traits<charT> )>
class basic_ios : public ios_base {
public:
basic_ios(const basic_ios&) = delete;
basic_ios& operator=(const basic_ios&) = delete;
deleted function은 private
이 아닌 public
으로 선언하는 것이 관례이다.
그 이유는 deleted function을 외부에서 호출했을 때 C++ 컴파일러가 함수의 접근성을 먼저 확인한 뒤에 삭제 여부를 확인하기 때문이다.
즉, private
에 deleted function을 선언하면 호출한 함수가 private
이어서 에러가 난 것인지 deleted function이어서 에러가 난 것인지 혼동을 줄 수 있다.
deleted function의 장점
deleted function의 장점은 다음과 같다.
의도가 명확함
deleted function을 사용하면 삭제된 함수를 사용했다는 에러 메시지를 통해 의도를 명확하게 알 수 있다.
에러를 발견하는 시점이 빠름
deleted function을 호출할 경우, 우리는 컴파일 과정에서 에러 메시지를 확인할 수 있다.
하지만 정의되지 않은 private function을 호출할 경우, 링킹 과정에 가서야 에러 메시지를 확인할 수 있다.
즉, deleted function을 사용하면 더 빨리 에러를 발견할 수 있다.
어떤 함수도 삭제할 수 있음
정의되지 않은 private function은 오직 클래스 멤버 함수에서만 사용할 수 있었다. 하지만 deleted function은 어디에서나 사용이 가능하다.
deleted function은 함수 오버로딩에도 유용하게 사용할 수 있다.
// 정수 값을 하나 받고 그 값이 행운의 번호인지의 여부를 돌려주는 비멤버 함수
bool isLucky(int number);
// 인자들이 int가 아니지만 암묵적으로 형변환되어 함수가 호출됨
if (isLucky('a'))
if (isLucky(true))
if (isLucky(3.5f))
// 함수 오버로딩을 통해 타입들을 배제할 수 있음
bool isLucky(char) = delete;
bool isLucky(bool) = delete;
bool isLucky(double) = delete; // double을 삭제하면 float도 배제됨
if (isLucky('a')) // 에러
if (isLucky(true)) // 에러
if (isLucky(3.5f)) // 에러
원치 않는 템플릿 인스턴스화를 방지함
deleted function은 원치 않는 템플릿 인스턴스화를 방지할 수 있다.
예를 들어 내장 포인터들을 다루는 템플릿을 구현할 때 다른 포인터들과 달리 특별한 처리가 필요한 void*
나 char*
를 삭제할 수 있다.
template <typename T>
void processPointer(T* ptr);
template <>
void processPointer<void>(void*) = delete;
template <>
void processPointer<char>(char*) = delete;
// 해당 타입을 배제할 때는 const 키워드, 나아가 volatile 키워드까지 삭제해야 함
template <>
void processPointer<const void>(const void*) = delete;
template <>
void processPointer<const char>(const char*) = delete;
클래스 안의 함수 템플릿 인스턴스화를 막고 싶으면 정의되지 않은 private function으로는 한계가 있다.
class Widget {
public:
template <typename T>
void processPointer(T* ptr) {}
private:
template <>
void processPointer<void>(void*); //에러
};
같은 이름의 함수 템플릿 중 일부를 못쓰게 하고 싶다고 접근 지정자를 서로 다르게 선언할 수는 없기 때문이다.
이때 deleted function을 사용하면 원하는 함수 템플릿 인스턴스화를 막을 수 있다.
class Widget {
public:
template <typename T>
void processPointer(T* ptr) {}
template <>
void processPointer<void>(void*) = delete;
};