C++에서는 클래스를 정의하면 컴파일러가 기본적으로 복사 생성자(copy constructor)와 복사 대입 연산자(copy assignment operator)를 자동으로 생성해 줍니다. 덕분에 객체를 손쉽게 복사할 수 있지만, 파일 핸들이나 동적 메모리처럼 리소스를 직접 관리하는 클래스, 혹은 싱글턴(Singleton) 패턴처럼 인스턴스가 하나만 존재해야 하는 클래스에서는 객체 복사가 오히려 버그의 원인이 될 수 있습니다.
이번 글에서는 C++에서 객체 복사를 막는 대표적인 두 가지 방법을 예제 코드와 함께 살펴보겠습니다.
방법 1: 복사 생성자와 대입 연산자를 private으로 선언
가장 간단한 방법은 복사 생성자와 복사 대입 연산자를 private 영역에 선언하는 것입니다. 이렇게 하면 클래스 외부에서 복사를 시도하는 순간 컴파일 에러가 발생하여 복사 자체를 원천적으로 차단할 수 있습니다.
예제 코드
#include <iostream>
using namespace std;
class MyClass {
int x;
public:
MyClass() {
// 매개변수 없는 기본 생성자
}
MyClass(int y): x(y) {
}
private:
MyClass(const MyClass& obj) : x(obj.x) {
// private 복사 생성자
}
MyClass& operator=(const MyClass& tmp_obj) {
// private 복사 대입 연산자(오버로딩)
x = tmp_obj.x;
return *this;
}
};
main() {
MyClass ob(50);
MyClass ob2(ob); // 복사 생성자 호출
ob2 = ob; // 복사 대입 연산자 호출
}실행 결과
위 코드를 컴파일하면 private 멤버에 접근하려 했다는 오류가 발생합니다.
[Error] 'MyClass::MyClass(const MyClass&)' is private [Error] within this context [Error] 'MyClass& MyClass::operator=(const MyClass&)' is private [Error] within this context
방법 2: 더미 클래스를 상속받아 복사 차단
두 번째 방법은 복사 생성자와 대입 연산자를 private으로 선언해 둔 더미(Dummy) 클래스를 만들고, 복사를 금지하고 싶은 클래스가 이 더미 클래스를 상속받도록 하는 것입니다.
컴파일러가 파생 클래스의 복사 생성자와 대입 연산자를 자동으로 생성(합성)할 때 기반 클래스의 해당 함수를 호출하게 되는데, 이 함수들이 private이므로 접근 권한 오류가 발생하면서 결과적으로 복사가 차단됩니다. 여러 클래스에 복사 금지 규칙을 적용해야 할 때 코드 재사용 측면에서 유용한 방식입니다.
예제 코드
#include <iostream>
using namespace std;
class DummyClass {
public:
DummyClass() {
}
private:
DummyClass(const DummyClass& temp_obj) {
}
DummyClass& operator=(const DummyClass& temp_obj) {
}
};
class MyClass : public DummyClass {
int x;
public:
MyClass() { }
MyClass(int y) : x(y) {
}
};
int main() {
MyClass ob1(50);
MyClass ob2(ob1); // 복사 생성자 호출
ob2 = ob1; // 복사 대입 연산자 호출
}실행 결과
[Error] 'DummyClass::DummyClass(const DummyClass&)' is private [Error] within this context In function 'int main()': [Note] synthesized method 'MyClass::MyClass(const MyClass&)' first required here In member function 'MyClass& MyClass::operator=(const MyClass&)': [Error] 'DummyClass& DummyClass::operator=(const DummyClass&)' is private [Error] within this context In function 'int main()': [Note] synthesized method 'MyClass& MyClass::operator=(const MyClass&)' first required here
참고: C++11의 '= delete' 문법 활용
C++11 이상을 사용한다면 위 두 방법보다 더 명시적이고 권장되는 문법인 = delete를 활용할 수 있습니다. 함수를 아예 삭제(deleted) 처리하면 컴파일러가 해당 함수를 생성하지 않으며, 호출 시점에 명확한 에러 메시지를 제공합니다.
class MyClass {
int x;
public:
MyClass() { }
MyClass(int y) : x(y) { }
MyClass(const MyClass&) = delete; // 복사 생성자 삭제
MyClass& operator=(const MyClass&) = delete; // 복사 대입 연산자 삭제
};이 방식은 의도를 코드에 그대로 드러낼 수 있어 가독성과 유지보수성 면에서 가장 좋습니다. 레거시 환경이 아니라면 가능한 한 = delete 사용을 권장합니다.
마무리
객체 복사를 허용하지 않아야 하는 상황이라면, private 접근 지정자를 활용한 전통적인 방법 두 가지 모두 유효합니다. 다만 최신 C++ 표준을 사용할 수 있는 환경이라면 = delete 문법이 가장 깔끔하고 안전한 선택입니다. 상황에 맞는 방법을 골라 의도치 않은 객체 복사로 인한 버그를 미리 예방하시기 바랍니다.