Post List

2015년 3월 31일 화요일

item 11: 함수의 private 선언(+미구현) 보다 = delete를 사용하자.

작가
Meyers, Scott
출판
O'ReillyMedia
발매
2014.12.12
평점








Effective Modern C++ - Scott Meyers
Item 11: Prefer  deleted functions to private undefined ones.

Item 11. 함수의 private 선언 (+미구현) 보다 = delete를 사용하자.

사용하지 않을 함수는 만들지 않으면 됩니다. 너무나도 간단한 이야기죠.
하지만, 개발자가 만들지 않더라도 생성되거나 생성 가능성이 있는 함수들이 있습니다.

첫째로는 일정 조건에 만족할 경우 컴파일러가 자동으로 만들어주는 함수들이 있습니다.
(이것을 특별 멤버 함수(Special Member Functions)라고 합니다.
e.g. 생성자, 소멸자, 복사생성자, 이동 생성자,  복사 할당 연산자, 이동 할당 연산자)

둘째로는 암묵적 형변환에 의해서 원하지 않는 인자값들이 함수로 전달될 경우입니다.
(e.g. int 값만을 전달받고 싶은데, doublebool 등의 전달을 막을 경우)

세번째로는 template이 개발자가 허용하지 않는 타입으로의 의 인스턴스화(Instantiation) 하는 경우입니다.
(e.g. 함수내에서 T의 + 연산자를 사용했는데, + 연산자가 없는 타입으로 인스턴스화할 경우)

첫번째 경우는 C++98 스타일로 막을 수 있지만, 그래도 사용할 때에는 Link 단계에서 오류를 발생시킵니다.
두번째, 세번째는 C++98 스타일에서는 막을 수가 없었습니다.

C++11에 함수선언에 = delete를 붙임으로 해당 형식의 함수를 삭제할 수 있습니다.
만약 사용하게 된다면 컴파일 타임에 오류를 발생시킵니다.

위에 언급한 3가지 경우에 대해서 살펴보겠습니다.

1. 컴파일러가 자동으로 생성해주는 함수를 삭제

예를 들어서 STL에 포함된 iostream의 경우 상위 class는 basic_ios 라는 template class입니다.
istream과 ostream 모두 복사가 불가능하도록 정의되어 있습니다.
C++98에서는 아래와 같이 구현되어 있습니다.

template <class T, class traits = char_traits<T>>
class basic_ios : public ios_basic
{
private:
    basic_ios(const basic_ios&);            // not defined
    basic_iosoperator=(const basic_ios&); // not defined
};

private영역에 선언이 되어 있어서 해당 class밖에서는 당연히 호출을 못하겠지만,
만약 해당 class의 멤버 함수라던지, 이 class의 friend 함수에서 호출 할 경우 링크 에러가 발생합니다.

이것을 C++11에서는 public영역으로 옮긴 후 = delete를 붙여주면 됩니다.

template <class T, class traits = char_traits<T>>
class basic_ios : public ios_basic
{
public:
    basic_ios(const basic_ios&)            = delete;               
    basic_iosoperator=(const basic_ios&) = delete;
};

public영역으로 옮긴 이유는 함수 호출이 발생했을 때 C++ 컴파일러는 해당 함수의 접근 가능한지를 먼저 체크합니다.
즉 public인지 private인지를 먼저 체크합니다.
이제는 해당 함수에 접근하고자 할 경우 컴파일 타임에 오류를 발생합니다.

2. 함수 인자의 원하지 않는 암묵적 변환을 막고 싶을 경우

bool isAuthorizedUser(int nID);

int형의 ID 값을 입력받아서 인가된 사용자인지 체크하는 함수가 있다고 가정하겠습니다.
인자의 타입으로 int를 사용했지만, 암묵적 형변환이 일어나는 이떤 값도 전달이 가능합니다.

bool RET1 = isAuthorizedUser('a');
bool RET2 = isAuthorizedUser(true);
bool RET3 = isAuthorizedUser(3.5);

모두 정상적으로 컴파일되고 실행되지만, 이러한 타입의 접근을 막고 싶을 경우
delete를 이용하여 오버로딩된 함수를 삭제 할 수 있습니다.

bool isAuthorizedUser(int nID);
bool isAuthorizedUser(char)   = delete;
bool isAuthorizedUser(bool)   = delete;
bool isAuthorizedUser(double) = delete;

위와 같이 선언되었을 때 char 타입의 인자가 들어오면,
오버로드 결정 규칙 (Overload Resolution Rule)에 의해서 char 인자의 오버로드 함수가 호출이 됩니다.
해당 함수는 삭제되었다고 컴파일러가 판단을 하여서 오류를 발생시킵니다.

3. 템플릿 인스턴스화 (template instantiation)에서 특정 타입을 제한할 경우

배열의 특정 위치를 포인터로 받아서 그 다음 개체를 return하는 template함수를 예로 들어보겠습니다.

template <typename T>
T nextObjectInArray(T* ptr) { return *(ptr + 1); };

C의 포인터 중 특이한 포인터가 2개 있습니다.
1. void* : 원래의 값을 * 연산자(dereference)로 찾을수 없으며, 증가, 감소 연산도 안됩니다.
2. char* : 전통 C에서는 문자열로 취급을 하기 때문에 개별 포인터로의 개념과는 조금 다릅니다.

char* 의 경우는 위의 예제에서 정상적으로 동작하지만,
void*와 char*에 대해서는 해당 template의 인스턴스화를 허용하지 않고자 할 경우 다음과 같이 선언을 해주면 됩니다.

template <>
void nextObjectInArray<void>(void* ptr) = delete;

template <>
char nextObjectInArray<char>(char* ptr) = delete;

constvolatile 등의 키워드도 고려해야 한다면 해당 인스턴스화도 막아줘야 하며,
std::wchar_tstd::char16_tstd::char32_t 등도 넣어야 합니다.
(귀찮아지죠 ? ㅎ 좀 많이)

template 인스턴스화를 C++98 스타일로 막을 수 있지 않느냐는 의구심이 생길수도 있습니다.

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

class내부에서 선언한 template함수의 경우 template 특수화(specialization) 함수를 private로 넣으면 어떨까라고 생각해 볼수 있겠지만,
컴파일러에게 허용되지 않는 코드입니다.
template함수와 그의 특수화한 함수들은 동일한 접근 레벨에 있어야 합니다.

Things to Remember

private 선언(+미구현) 보다는 = delete를 사용하는게 더 좋습니다.

* 모든 종류의 함수들에 대해서 = delete 사용이 가능합니다.
  (class 멤버 함수, 일반 함수, template specialization ...)


댓글 없음:

댓글 쓰기