ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective Modern C++] private와 delete
    C++/Effective Modern C++ 2022. 4. 22. 17:37

    함수를 하나 정의해보겠습니다.

    이 함수는 정수를 하나 받아서 당첨 번호인지 반환해주는 함수입니다.

    bool is_chosen(int number) { ... }

    이 함수는 본래의 의도와는 다른 방향으로 사용될 여지가 존재합니다.

    if (is_chosen('a')) { ... }
    if (is_chosen(true)) { ... }
    if (is_chosen(3.5)) { ... }

    위와 같은 함수 호출은 인자들이 묵시적으로 int로 변환되어 전달됩니다.

    이런 상황을 방지하기 위해, 개발자는 특정 형식들에 대한 함수의 오버로딩을 삭제할 수 있습니다.

    이번 글에서는 함수의 삭제에 대해 살펴보겠습니다.

     


     

    1. 함수 삭제하기

     

    더보기

    서론에 언급한, is_chosen(int)함수가 다른 형식으로 호출되는 것을 막기 위해서는 아래와 같이 원하지 않는 형식에 대한 함수를 삭제하는 것으로 해결 가능합니다.

    bool is_chosen(char) = delete;
    bool is_chosen(bool) = delete;
    bool is_chosen(double) = delete;

    함수의 정의와 함께 = delete를 붙이는 것으로 함수를 삭제할 수 있습니다.

    위와 같은 코드를 is_chosen과 함께 정의할 경우, char, bool, double, float을 파라미터로 하는 is_chosen함수의 호출을 막을 수 있습니다. float의 경우, float을 인자로 하는 오버로딩이 없을 경우 double로 캐스팅 되어 호출하게 되고, 이것이 삭제된 함수를 참조하여 호출이 되지 않습니다. 

     

    2. 삭제와 접근 차단

     

    더보기

    메소드의 경우, 클라이언트에서 사용되길 원하지 않는 형식의 경우 private로 선언하는 방식으로 구현할 수 있습니다.

    예를 들어 C++98에서의 basic_ios (iostream의 뿌리가 되는 클래스 템플릿)은 객체의 복사를 방지하기 위해 특정 함수를 다음과 같이 선언했습니다.

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

    not defined 주석은 표준에도 존재합니다.

    private영역에서 선언되었기 때문에 클라이언트에서 접근이 불가하며, 접근이 가능한 영역(멤버 함수 혹은 friend 함수)에서 호출할 경우 정의되어 있지 않기 때문에 링크가 실패합니다.

     

    C++11에서는 위 코드가 아래와 같이 변경되었습니다.

    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로 선언되었던 복사 관련 함수들을 public으로 옮기고 삭제한 것을 볼 수 있습니다.

    위 두 코드의 차이점은 아래와 같습니다.

    • 함수가 삭제되었기 때문에, 멤버 함수나 friend 함수에서 호출하더라도 컴파일이 되지 않습니다. 링크 시점에서 문제가 발생했던 위 경우보다 안정적이라고 볼 수 있습니다.
    • 멤버 함수가 아닌 함수의 경우 위와 같이 private로 선언하여 호출을 막을 수 없는데, 삭제를 이용하면 함수에 대한 사용을 막을 수 있습니다.
    • private 선언의 경우 함수에 대한 접근이 불가능한 경우이고, delete는 함수를 삭제하여 사용하지 못하게 하는 점에서 다릅니다. 이는 개발자의 의도를 명확하게 표현하는 데 도움이 될 수 있습니다.

    세 번째 차이점의 경우, 컴파일러는 함수의 호출에 있어 삭제 여부보다 먼저 접근 여부를 점검합니다.

    함수를 삭제하더라도 private영역에 선언할 경우 삭제 메시지가 아닌 접근 불가 메시지가 호출되기 때문에, 멤버 함수를 삭제할 경우 public영역에 선언하는 것이 좋겠습니다.

     

    3. 예시 (void*와 char*)

     

    더보기

    delete를 이용해 특정 템플릿 인스턴스화를 막을 수 있습니다.

    예를 들어, 다음과 같이 특정 포인터를 다루는 템플릿을 가정해보도록 하겠습니다.

    template<typename T>
    void process_pointer(T* ptr) { ... }

    포인터의 경우, void와 char포인터는 다른 포인터들과는 조금 다릅니다.

    void*는 역참조, 증감이 불가하고, char*는 C스타일 문자열을 표현하는 점에서 다릅니다.

    위와 같은 특별한 포인터들은 특수화를 통해 따로 처리를 해야하는데, 이번에 살펴볼 예제의 process_pointer 함수 템플릿은 호출을 막는 방향으로 처리한다고 가정하겠습니다.

    이것은 아래와 같이 처리가 가능합니다.

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

    여기서 더 확실하게 하려면 const void*, const char*, const volatile void* 등의 형식과, wchar_t, char16_t, char32_t와 같은 표준 문자 형식에 대한 포인터도 삭제하는 것이 완벽할 것입니다.

     

    이 예제에서 살펴보아야 할 것은, 클래스 내부의 함수 템플릿에 대한 인스턴스화를 막는 목적으로 private를 사용할 수 없다는 점 입니다.

    위의 process_pointer 함수 템플릿이 특정 클래스의 멤버 함수일 경우, 아래와 같이 private를 사용하는 것은 불가합니다.

    class MyClass {
    public:
        ...
        template<typename T>
        void process_pointer(T* ptr) { ... }
    private:
        template<>
        void process_pointer<void>(void*);
    };

    템플릿 특수화는 클래스 범위가 아닌, 네임스페이스에서 작성되어야 하기 때문입니다.

    위의 경우, 멤버 함수를 네임스페이스에서 특수화 및 삭제 하는것으로 구현 가능합니다.

    class MyClass {
    public:
        ...
        template<typename T>
        void process_pointer(T* ptr) { ... }
    };
    
    template<>
    void MyClass::process_pointer<void>(void*) = delete;

     


     

    함수를 private로 선언해서 접근을 막는것은 delete를 흉내내는 것이기 때문에, 부족한 부분이 존재합니다.

    위 문단들의 예제처럼 선언 불가능한 영역도 존재하고, 컴파일 시점 이후에 오류가 발견되는 경우도 존재합니다.

    클라이언트의 호출을 막아야 하는 특수한 경우의 함수는 delete로 삭제하는 것이 좋겠습니다.

     

    감사합니다.

    댓글

Designed by Tistory.