C++ 개발 중, 구조체나 클래스가 한 번만 생성될 것으로 예상했는데, 실제로는 여러 번 생성자와 소멸자가 호출되는 상황을 겪어본 적 있으신가요?
예를 들어, 로그를 찍어보면 이런 출력이 반복됩니다:
MyStruct Constructor
MyStruct Copy Constructor
MyStruct Destructor
도대체 왜 이렇게 객체가 복사되고 생성되는 걸까? — 이 문제는 퍼포먼스 이슈, 메모리 낭비, 논리 오류로 이어질 수 있으므로 반드시 원인을 파악해야 합니다.
C++은 객체가 함수에 전달되거나 반환될 때 복사 생성자 또는 이동 생성자를 자동으로 호출합니다. 그러나 이 규칙은 매우 복잡하며, 컴파일러 최적화 여부, 참조 타입의 사용 여부, RVO(Return Value Optimization) 등에 따라 달라집니다.
이런 동작은 명시하지 않아도 자동으로 발생할 수 있어 디버깅이 어렵고, 의도하지 않은 오버헤드를 유발합니다.
void foo(MyStruct obj); // 복사 생성자 호출
MyStruct bar() {
MyStruct obj;
return obj; // 복사 또는 이동
}
foo(MyStruct()); // 이동 생성자 또는 복사 생성자 호출
일부 컴파일러에서는 Return Value Optimization이 적용되지 않아, 불필요한 복사가 발생합니다.
std::vector<MyStruct> vec;
vec.push_back(obj); // 복사
vec.emplace_back(obj); // C++11 이상에서는 이동 또는 직접 생성
클래스 A가 클래스 B의 객체를 멤버로 포함할 경우, B의 생성자가 암묵적으로 여러 번 호출될 수 있습니다.
클래스에 로그를 삽입하면 생성자 호출을 실시간으로 확인할 수 있습니다.
struct MyStruct {
MyStruct() { std::cout << "Constructor\n"; }
MyStruct(const MyStruct&) { std::cout << "Copy Constructor\n"; }
MyStruct(MyStruct&&) noexcept { std::cout << "Move Constructor\n"; }
~MyStruct() { std::cout << "Destructor\n"; }
};
또한, Clang의 -fsanitize=address, Valgrind, Visual Studio의 디버거 등을 활용하면 메모리 및 객체 생성 흐름을 시각적으로 파악할 수 있습니다.
C++의 생성자 호출은 단순히 객체를 만드는 작업이 아닙니다. 메모리, 성능, 논리 안정성과 직결된 핵심 요소입니다. 의도하지 않은 생성자 호출은 코드의 퍼포먼스를 갉아먹고, 디버깅을 어렵게 만들며, 숨겨진 버그의 원인이 될 수 있습니다.
지금 당신의 코드에서 생성자가 몇 번 호출되고 있는지, 직접 확인해보세요. 그리고 지금까지의 개발 습관을 점검해 보세요. 복잡한 코드를 만드는 건 컴파일러가 아니라 개발자 자신입니다.