Computer >> 컴퓨터 >  >> 프로그래밍 >> C++

C++에서 동적 할당으로만 객체 생성을 허용하는 클래스 만드는 방법

C++에서 동적 메모리 할당(new)을 통해서만 객체를 생성할 수 있고, 스택에 직접 객체를 선언하는 것은 허용되지 않는 클래스를 만들어야 하는 경우가 있습니다. 스마트 포인터나 참조 카운팅 기반 객체처럼 힙 할당을 강제하고 싶을 때 유용한 기법인데요, 이번 글에서는 그 구현 방법을 예제와 함께 자세히 살펴보겠습니다.

핵심 원리: private 소멸자 활용

방법은 의외로 간단합니다. 바로 소멸자(destructor)를 private 영역에 선언하는 것입니다.

소멸자가 private이면 컴파일러는 스택 기반의 일반 객체 생성 코드를 컴파일할 때 오류를 발생시킵니다. 그 이유는 다음과 같습니다.

  • 스택에 생성된 객체는 범위(scope)를 벗어날 때 컴파일러가 자동으로 소멸자를 호출하여 제거해야 합니다.
  • 그런데 소멸자가 private이면 외부에서 접근할 수 없으므로, 컴파일러는 이 작업을 수행할 수 없고 결국 컴파일 에러를 냅니다.
  • 반면 new로 힙에 할당된 객체는 프로그래머가 명시적으로 delete를 호출해야 하며, 컴파일러가 소멸 시점에 관여하지 않으므로 동적 생성은 문제없이 허용됩니다.

다만 이 방식에는 한 가지 주의점이 있습니다. 소멸자가 private이기 때문에 사용자가 delete를 직접 호출할 수 없어 메모리 누수가 발생할 수 있다는 점입니다. 이를 해결하기 위해 friend 함수를 두어 객체 삭제를 전담하게 만듭니다.

구현 예제

다음 코드를 통해 동작 방식을 확인해 보겠습니다.

#include<iostream>
using namespace std;

class NoDirectObjClass {
    private:
        ~NoDirectObjClass() {
            cout << "Destroying NoDirectObjClass object" << endl;
        }
    public:
        NoDirectObjClass() {
            cout << "Creating object" << endl;
        }
    friend void friend_destructor(NoDirectObjClass*);
};

void friend_destructor(NoDirectObjClass* p) {
    delete p;
    cout << "Destroying object using friend" << endl;
}

int main(){
    NoDirectObjClass *p = new NoDirectObjClass; // 동적 할당은 허용됨
    friend_destructor(p); // friend 함수로 안전하게 해제
}

실행 결과

Creating object
Destroying NoDirectObjClass object
Destroying object using friend

new를 통한 동적 할당은 정상적으로 수행되었고, friend 함수인 friend_destructor()가 private 소멸자에 접근하여 객체를 안전하게 해제한 것을 확인할 수 있습니다.

스택에 직접 객체를 생성하면 어떻게 될까?

이번에는 동적 할당 없이 스택에 객체를 직접 선언해 보겠습니다.

int main(){
    NoDirectObjClass t1; // 컴파일 오류 발생!
}

컴파일 결과

main.cpp: In function 'int main()':
main.cpp:22:22: error: 'NoDirectObjClass::~NoDirectObjClass()' is private
within this context
    NoDirectObjClass t1;
                      ^~
main.cpp:6:9: note: declared private here
         ~NoDirectObjClass() {
         ^

예상대로 "소멸자가 private이므로 이 컨텍스트에서 접근할 수 없다"는 컴파일 오류가 발생합니다. 컴파일러가 스택 객체를 자동으로 소멸시키려다 private 소멸자에 접근하지 못하기 때문입니다. 이렇게 하면 개발자가 실수로 스택에 객체를 생성하는 것을 컴파일 단계에서 원천적으로 차단할 수 있습니다.

마무리 및 추가 팁

정리하면, 소멸자를 private으로 선언하면 스택 객체 생성이 차단되고, friend 함수(또는 정적 멤버 함수)를 통해 객체 해제 경로를 제공하면 메모리 누수도 방지할 수 있습니다. 실무에서는 friend 함수 대신 클래스 내부에 static void destroy(NoDirectObjClass* p) 같은 정적 멤버 함수를 두는 방식도 널리 사용되며, 팩토리 패턴과 결합하면 객체의 생성과 소멸을 완전히 통제하는 더 견고한 설계를 만들 수 있습니다.