[Modern C++] 항목 11: 정의되지 않은 비공개 함수보다 삭제된 함수를 선호하라
카테고리: Cpp
이 글은 아래의 책을 정리하였습니다. 이펙티브 모던 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를 사용하면, 비멤버 함수와 템플릿 인스턴스를 비롯한 그 어떤 함수도 삭제할 수 있다.
댓글남기기