스토리텔링 개발자

[Effective Modern C++] 11. 삭제된 함수(deleted function) 본문

Effective C++/Effective Modern C++

[Effective Modern C++] 11. 삭제된 함수(deleted function)

김디트 2025. 2. 19. 10:45
728x90

항목 11. 정의되지 않은 private 함수보다 삭제된 함수를 선호하라

 

 

 

코드의 특정 함수를 호출하지 못하도록 하기
  • 그냥 그 함수를 선언하지 않는다.
    • 특수 멤버 함수들에서는 그게 쉽지 않다.
    • 즉, C++이 필요에 따라 자동으로 작성하는 멤버 함수들.
    • 예를 들면 복사 생성자, 복사 대입 연산자.
  • C++98에서의 접근 방식
    • 해당 함수들을 private 선언하고 정의하지 않는다.
    • private에 선언되어 있으므로 호출할 수 없다.
    • 정의하지 않았으므로 호출 시 링크 실패한다.
    • C++98 표준 입출력 스트림 객체는 복사를 방지하도록 구현되어 있다.
template<class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base
{
public:
    ...
private:
    basic_ios(const basic_ios&); // not defined
    basic_ios& operator=(const basic_ios&); // not defined
};
  • 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;
    ...
};

 

 

 

함수 private 선언과 함수 delete의 차이
  • 삭제된 함수는 어떤 방법으로든 사용할 수 없다.
    • 즉, 컴파일이 실패한다.
    • C++98 방식은 링크 시점에 가서야 발견할 수 있다.
  • 삭제된 함수는 public으로 선언하는 것이 관례이다.
    • 멤버 함수 호출 시, C++은 함수의 접근성을 먼저 체크한 후 삭제 여부를 체크한다.
    • 몇몇 컴파일러는 private를 호출했음을 먼저 에러로 고지하며, 그래서는 delete 된 함수인지 바로 알기 힘들다.
  • 삭제된 함수는 어떤 함수도 삭제할 수 있다.
    • C++98 방식은 멤버 함수에만 적용할 수 있다.
bool isLucky(int number);

// 암시적 변환으로 인해 아래의 함수는 모두 컴파일 성공한다.
if(isLucky('a')) ...
if(isLucky(true)) ...
if(isLucky(3.5)) ...

// 아래처럼 하면 해결할 수 있다.

bool isLucky(int number);
bool isLucky(char) = delete;
bool isLucky(bool) = delete;
bool isLucky(double) = delete;
  • 삭제된 함수들은 사용할 수 없더라도 프로그램의 일부이다.
    • 즉, 함수 오버로드의 후보이다.
    • 그러므로 위의 문제가 해결이 되는 것이다.
  • 삭제된 함수는 템플릿 인스턴스화를 방지할 수 있다.
template<typename T>
void processPointer(T* ptr);

// 특별한 포인터 두 가지(void*, char*)에 대해 호출을 거부하고 싶다.

// C++11에서는 delete 처리하면 된다.
template<>
void processPointer<void>(void*) = delete;
template<>
void processPointer<void>(char*) = delete;
// const 버전도 delete 처리한다.
template<>
void processPointer<void>(const void*) = delete;
template<>
void processPointer<void>(const char*) = delete;
// 더 철저히 하려면
// const volatile void* / const volatile char*
// wchar_t, char16_t, char32_t
// 들도 delete 처리해야 할 것이다.

// C++98 관행으로는 처리할 수 없다.
class Widget
{
public:
    ...
    template<typename T>
    void processPointer(T* ptr)
    { ... }
private:
    template<>
    void processPointer<void>(void*); // 에러!
};
  • private로 정의 없이 선언이 불가능한 이유
    • 템플릿 특수화는 반드시 클래스 범위가 아니라 네임스페이스 범위에서 작성해야 한다는 규칙이 있기 때문이다.
728x90
Comments