[Modern C++] 항목 11: 정의되지 않은 비공개 함수보다 삭제된 함수를 선호하라

게시:     수정

카테고리:

태그: ,

이 글은 아래의 책을 정리하였습니다. 이펙티브 모던 C++, 스콧 마이어스 저자, 류광 번역

가독성이 떨어지는 직역들을 수정하며 정리하였습니다. e.g. 연역 → 추론, 중복적재 → 오버로딩

📦 3. 현대적 C++에 적응하기

👉🏻 항목 11: 정의되지 않은 비공개 함수보다 삭제된 함수를 선호하라

📌 함수 사용 금지 방법

  • 선언하지 않기: C++이 자동 생성하는 특수 멤버 함수들은 이 방법이 통하지 않는다. (e.g. 복사 생성자, 복사 대입 연산자)
  • private로 선언 후, 정의하지 않기: C++98 방식
  • = delete 사용: C++11 방식

❌ C++98 방식: private + 미정의

template <class charT, class traits = char_traits<charT>>
class basic_ios : public ios_base {
public:
	...

private:
	basic_ios(const basic_ios&); // 정의 안함
	basic_ios& operator=(const basic_ios&); // 정의 안함
};
  • 클라이언트의 호출을 차단할 수 있다.
  • 멤버 함수나 friend 함수에서 호출 시, 링크 오류가 발견된다.
    • ⚠️ 부적절한 사용이, 링크 시점에서야 발견된다!

✅ 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;
	...
};
  • 선언 끝에 = delete를 붙이면 삭제된 함수(deleted function) 가 된다.
  • 어떤 방법으로도 사용할 수 없다.
    • 멤버 함수나 friend 함수에서 호출해도, 컴파일 시점에 즉시 실패한다.
  • 삭제된 함수는 public으로 선언하는 것이 관례이다.
    • C++은 함수의 접근성을 먼저 점검 후, 삭제 여부를 점검한다.
    • 그러므로, public으로 선언해야 명확한 오류 메시지가 나온다!

🔧 = delete: 비멤버 함수에도 적용 가능

bool isLucky(int number); // 행운의 번호인지 판별하는 비멤버 함수

if (isLucky('a')) ... // 의도치 않은 사용법!
if (isLucky(true)) ... // 의도치 않은 사용법!
if (isLucky(3.5)) ... // 의도치 않은 사용법!
  • C에서 물려받은 유산으로, 수치로 간주될 여지가 있는 형식은 암묵적으로 int로 변환된다.
  • 비멤버 함수이므로, “private + 미정의” 방식을 사용할 수 없다.
bool isLucky(int number);

bool isLucky(char) = delete;   // char을 받는 함수 삭제
bool isLucky(bool) = delete;   // bool을 받는 함수 삭제
bool isLucky(double) = delete; // float/double을 받는 함수 삭제

if (isLucky('a')) ... // ⚠️ 오류! 삭제된 함수를 호출하려 함
if (isLucky(true)) ... // ⚠️ 오류!
if (isLucky(3.5)) ... // ⚠️ 오류!
  • 의도치 않은 호출을 막으려면, 해당 형식의 오버로딩을 명시적으로 삭제한다.
  • double 오버로딩을 삭제하면, float/double 모두 삭제된다.
    • float → int, float → double 변환 모두 되기에, C++은 double로의 변환을 선택한다.

🔧 = delete: 템플릿 인스턴스화 방지

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

// void*, char* 버전 삭제
template<>
void processPointer<void>(void*) = delete;

template<>
void processPointer<char>(char*) = delete;

// const 버전도 삭제
template<>
void processPointer<const void>(const void*) = delete;

template<>
void processPointer<const char>(const char*) = delete;
  • 특별한 포인터 두 가지:
    • void* 포인터: 역참조, 증가, 감소 불가
    • char* 포인터: 개별 문자가 아닌, C 스타일 문자열
  • const volatile void*, const volatile char*, wchar_t, char16_t, char32_t 포인터 버전도 철저히 삭제해도 좋다.
  • 해당 형식의 호출을 거부하기 위해, 해당 인스턴스를 삭제할 수 있다.
    • private + 미정의” 방식으로는 불가능한 일이다.

⚠️ 클래스 내 멤버 함수 템플릿에서의 차이

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

private:
	template<> // ⚠️ 오류! 내부에서 템플릿 특수화 불가
	void processPointer<void>(void*);
};
  • private 방식으로는 클래스 내 함수 템플릿의 특수화를 방지할 수 없다.
    • 템플릿 특수화는 클래스 범위가 아닌, 이름공간 범위에서 작성해야 한다!
class Widget {
public:
	...
	template<typename T>
	void processPointer(T* ptr) { ... }
};

template<> // ✅ 여전히 public이며, 삭제됨
void processPointer<void>(void*) = delete;

🧐 정리

  • private + 미정의보다 = delete를 선호하자.
  • = delete를 사용하면, 비멤버 함수와 템플릿 인스턴스를 비롯한 그 어떤 함수도 삭제할 수 있다.

Cpp 카테고리 내 다른 글 보러가기

댓글남기기