다른 개발자에게 코드를 제공할 때 특정 함수를 호출하지 못하게 하려면 해당 함수를 선언하지 않으면 된다. 그런데 C++이 자동으로 생성하는 멤버 함수들은 그럴수가 없다. 기본 생성자나 복사 생성자 같은것들 말이다.

함수의 사용을 막는 두 가지 방법에 대해서 알아보자.

 

방법 1 : private 접근 지정자와 함수의 정의를 없애기

C++98에서는 자동으로 생성하는 멤버 함수의 접근 지정자를 private으로 명시적 선언을 하되 정의를 하지 않아서 사용하지 못하게 하는 테크닉을 이용했다.

 

template<class charT, class traits = char_traits<charT>>
class basic_ios : public ios_base {
public:
    ...
private:
    basic_ios(const basic_ios&); // 정의X
    basic_ios& operator=(const basic_ios&); // 정의X
};

복사를 막기위해 private 접근 지정과 더불어서 복사 연산자와 복사 대입 연산자의 선언만 했기 때문에 호출 시도시 컴파일 에러가 발생하게 된다. 혹시라도 friend나 멤버 함수를 통한 접근을 시도하더라도 정의가 되어있지 않기 때문에 링크 에러가 발생한다.

이런 방법도 나쁘지 않지만 C++11은 좀더 명확한 목적을 가진 방법이 추가되었다.

 

방법 2 : 함수를 사용하지 않는다고 선언하기

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;
};

위처럼 아예 해당 함수를 사용하지 않는다고 선언하는 것이다.

방법 1과 결과적으로 크게 달라보이지 않아서 그저 취향 문제로 보일수도 있지만 매우 큰 차이가 하나 있다.

방법 1은 멤버 함수나 friend에서 혹시라도 접근하게 되면 링크 단계까지 가서야 문제가 파악되지만 방법 2는 컴파일 단계에서 바로 파악된다.

또한 삭제된 함수는 public으로 선언하는것이 관례이다. 컴파일러별로 함수의 삭제보다 접근 지정자의 문제를 먼저 걸고 넘어지는 경우가 있기 때문에 에러 메시지를 보고 사용자가 오해할 소지가 있기 때문이다.

 

또하나의 차이가 있다. 방법 1은 접근 지정자를 사용하기 때문에 오직 멤버 함수에만 적용이 가능하지만 방법 2는 어떤 함수도 삭제할 수 있다는 것이다.

 

bool isLucky(int number);

if (isLucky('a')) ... // 정수만 받아야 하는데..?
if (isLucky(true)) ...
if (isLucky(3.5)) ...

어떤 함수가 반드시 정수만 받아야 한다면 어떻게 해야할까?

방법 1이라면 해결 방법이 없다. 멤버 함수가 아니기 때문에 접근 지정자가 사용이 불가능하기 때문이다.

하지만 방법 2라면 타입별로 오버로딩하고 해당 함수들을 삭제처리 하면 된다.

 

bool isLucky(int number);
bool isLucky(char) = delete;
bool isLucky(bool) = delete;
bool isLucky(double) = delete; // float, double 둘 다 배제

삭제된 함수들은 비록 사용할수는 없어도 어쨌든 코드에 존재하는 프로그램의 일부이기 때문에 함수 오버로딩의 선택지에 포함이 된다. 하지만 사용하지 않는다고 선언했기 때문에 컴파일 에러가 발생하게 된다.

 

 

템플릿 특수화와 조합하면 원치 않은 템플릿의 인스턴스화도 막을 수 있다.

 

template<typename T>
void processPointer(T* ptr);

template<>
void processPointer<void>(void*) = delete;
// const void에 대해서도 처리해야 한다

template<>
void processPointer<char>(char*) = delete;
// const char에 대해서도 처리해야 한다

좀더 확실하게 하려면 const volatile등 관련된 모든 타입에 대한 특수화도 삭제해야 할 것이다.

 

 

클래스 내 함수 템플릿의 일부를 인스턴스화 시키지 않기 위해 부분 특수화를 적용하는 경우가 발생한다면 방법 1로는 해결할 수 없다는 단점도 있다.

 

class Widget {
public:
    template<typename T>
    void processPointer(T* ptr);
private:
    template<> // 오류 발생
    void processPointer<void>(void*);
};

이게 왜 안되냐면 템플릿 특수화는 클래스 범위가 아니라 이름 공간의 범위에서 작성해야 하기 때문이다.

 

class Widget {
public:
    template<typename T>
    void processPointer(T* ptr);
};

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

방법 2는 문제 없이 삭제할 수 있다. 방법 2가 방법 1에 비해 모든 면에서 예외없이 상위호환이기 때문에 함수를 삭제할거라면 정의되지 않은 비공개 함수대신 delete를 사용하면 된다.

 

◾ 정의되지 않은 비공개 함수보다 삭제된 함수를 선호하라.

◾ 비멤버 함수와 템플릿 인스턴스를 비롯한 그 어떤 함수도 삭제할 수 있다.

+ Recent posts