ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective C++] 06. 특수 멤버 함수의 자동 생성에 관하여 (2)
    C++/Effective C++ 2025. 12. 2. 17:05

    원제목 : Explicitly disallow the use of compiler-generated functions you do not want (컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자)

     

    지난 글 ([Effective C++] 05. 특수 멤버 함수의 자동 생성에 관하여) 에서 특수 멤버 함수의 자동 생성 조건과 주목해야 할 상황에 대해 알아보았습니다.

    특수 멤버 함수가 선언되어 있지 않을 때, 이것을 사용해야 하는 코드가 있을 경우 컴파일러가 자동으로 이를 생성합니다.

    하지만 특정한 목적이 있어 이러한 특수 멤버 함수가 호출되면 안 되는 경우가 있을 수 있습니다.

     

    이번 글에서는 특수 멤버 함수의 자동 생성을 막는 것에 대해 알아보도록 하겠습니다.

     


     

    1. C++11 이전 : private 멤버 함수

     

    더보기

    외부의 호출을 막는 방법 중 하나는 해당 멤버를 private로 선언하는 것 입니다.

    class Uncopyable{
    private:
      Uncopyable(const Uncopyable&);
      Uncopyable& operator=(const Uncopyable&);
    };

    복사 생성자와 복사 대입 연산자를 private로 선언한 예제입니다.

     

    위와 같이 private로 선언만 한 경우, 그리고 해당 클래스를 상속받는 경우 해당 특수 멤버 함수를 사용할 수 없게 할 수 있습니다.

     

    2. C++11 이후 : delete

     

    더보기

    위 문단에서 private로 특수 멤버 함수를 사용할 수 없게 하는 방법을 알아보았습니다.

    C++11이후에는 이를 더 간단하고, 명시적으로 사용할 수 있습니다.

    class MyClass{
    public:
      MyClass(const MyClass&) = delete;
      MyClass& operator=(const MyClass&) = delete;
    };

    delete키워드는 해당 함수를 사용하지 않음을 명식적으로 선언하는 키워드입니다.

    위 예제는 복사 함수들을 delete한 경우로, 해당 함수가 호출되는 상황에서 컴파일 에러를 발생시킵니다. 

     

    3. 언제 사용할까? : 기본 생성자

     

    더보기

    기본 생성자의 자동 생성은 클래스에 어떠한 생성자도 선언되어 있지 않을 경우 생성됩니다.

    즉, 특정한 매개변수를 받는 생성자가 존재할 경우 기본 생성자는 자동 생성되지 않습니다.

     

    여기에 더해 기본 생성자를 제거해야 하는 경우는 일반적으로 두 가지 경우입니다.

    첫 번째는 설계상의 이유입니다.

    class MyClass{
    public:
      MyClass() = delete;
      MyClass(int foo, string bar) : importantNum(foo), importantStr(bar) { ... }
    private:
      int importantNum;
      string importantStr;
    };

    클래스의 멤버 변수 중 초기화가 반드시 필요한 변수가 있을 경우를 생각할 수 있습니다.

    예제는 int와 string이지만, 특정 매개변수를 통해 초기화해야 하는 사용자 지정 클래스일 수 있습니다.

    이런 경우, 이 클래스가 상속을 통해 확장되더라도 기본 생성자를 사용하지 못 하도록 명시적으로 delete를 하는 것이 좋을 수 있습니다.

     

    두 번째는 정적 클래스를 사용할 경우입니다.

    class MathUtility{
    public:
      MathUtility() = delete;
      static double sqrt(double x);
      static double pow(double x, double y);
      // All members are static
      ...
    };

    만약 특정 클래스의 인스턴스화가 불필요할 경우 (예제와 같이 모든 함수가 static일 경우 등이 있습니다.)를 생각할 수 있습니다.

    이 경우 인스턴스가 생기는 상황 자체가 불필요한 메모리 낭비가 될 수 있습니다.

    따라서 어떠한 생성자도 선언하지 않고, 기본 생성자를 delete하여 인스턴스가 생성되지 않도록 하는 것이 좋습니다.

     

    4. 언제 사용할까? : 소멸자

     

    더보기

    소멸자를 제거하면 해당 객체를 스택에 생성하는 것이 불가능하게 됩니다.

    class MyClass {
    public:
      ~MyClass() = delete;
    };
    
    int main(){
      // Stack : Error!
      MyClass foo;
      // Heap : Able
      MyClass* bar = new MyClass();
    }

    이는 해당 객체를 힙에 생성하는 것을 강제하는 의미로 작용할 수 있습니다.

    하지만 delete를 할 수 없어 해당 메모리를 프로세스의 생명 주기 동안 점유하게 됩니다.

    또한, 메모리 외의 리소스 (네트워크, 버퍼 등)가 누수될 수 있어, 별도의 자원 관리 함수를 만들어야 합니다.

    객체의 사용이 중지된 이후 해당 정리 함수를 호출하는 것이 강제되어, 객체의 사용에 주의가 더 필요해집니다.

     

    정리하면 객체를 힙에 적재하여 생명 주기를 프로세스의 종료 시점까지 늘리는 데 사용할 수 있지만, 더욱 세심한 관리가 필요해집니다.

     

    5. 언제 사용할까? : 복사

     

    더보기

    복사를 제거할 경우 객체의 유일성을 보장하게 됩니다.

    만약 생성된 객체가 유일한 ID값을 가진다거나 하는 등의, 유일성을 보장해야 하는 경우에 사용할 수 있습니다.

    class MyClass{
    public:
      MyClass(const MyClass&) = delete;
      MyClass& operator=(const MyClass&) = delete;
    };

    복사 생성자와 복사 대입 연산자를 delete하는 코드입니다.

     

    복사를 허용하지 않는 예시는 unique_ptr이 있습니다.

    unique_ptr<int> foo(make_unique<int>(new int(5)));
    // Error
    unique_ptr<int> bar(foo);

     

    6. 언제 사용할까? : 이동

     

    더보기

    이동은 소유권을 이전하는 연산입니다.

    메모리의 주소가 변경되므로, 해당 값이 의미가 있는 경우 혹은 이동이 정의되지 못 하는 경우에 사용할 수 있습니다.

    이동 연산의 미사용은 다음과 같이 할 수 있습니다.

    class MyClass {
    public:
      MyClass(MyClass&& rhs) = delete;
      MyClass& operator=(MyClass&& rhs) = delete;
    };

    이동 생성자와 이동 대입 연산자를 delete하는 코드입니다.

     

    이동 연산이 제거된 예시는 mutex가 있습니다.

    mutex foo;
    // Error
    mutex bar = move(foo);

     


     

    "무언가를 알아서 해준다" 는 것은 양날의 검입니다.

    편의성이 향상되는 효과가 있는 반면, 오히려 의도와 멀어지는 효과가 일어날 수 있습니다.

    적절한 경우에만 사용이 되도록, 알아서 되는 것을 막는 방법도 숙지하는 것이 좋겠습니다.

     

    이번 글이 도움이 되셨기를 바랍니다.

    감사합니다.

    댓글

Designed by Tistory.