목차

  1. 문제 상황: 생성자가 왜 자꾸 호출될까?
  2. 객체 복사와 이동의 진실
  3. 생성자 호출의 주요 원인 6가지
  4. 불필요한 생성자 호출을 줄이는 실전 팁
  5. 디버깅 기법: 생성자 호출 추적하는 법
  6. 마무리: 똑똑한 객체 관리가 코드 품질을 결정한다

1. 문제 상황: 생성자가 왜 자꾸 호출될까?

C++ 개발 중, 구조체나 클래스가 한 번만 생성될 것으로 예상했는데, 실제로는 여러 번 생성자와 소멸자가 호출되는 상황을 겪어본 적 있으신가요?

예를 들어, 로그를 찍어보면 이런 출력이 반복됩니다:

MyStruct Constructor
MyStruct Copy Constructor
MyStruct Destructor

도대체 왜 이렇게 객체가 복사되고 생성되는 걸까? — 이 문제는 퍼포먼스 이슈, 메모리 낭비, 논리 오류로 이어질 수 있으므로 반드시 원인을 파악해야 합니다.


2. 객체 복사와 이동의 진실

C++은 객체가 함수에 전달되거나 반환될 때 복사 생성자 또는 이동 생성자를 자동으로 호출합니다. 그러나 이 규칙은 매우 복잡하며, 컴파일러 최적화 여부, 참조 타입의 사용 여부, RVO(Return Value Optimization) 등에 따라 달라집니다.

  • 복사 생성자: 기존 객체의 내용을 새 객체에 복사
  • 이동 생성자: 자원의 소유권을 이전 (보통 임시 객체에서 사용)

이런 동작은 명시하지 않아도 자동으로 발생할 수 있어 디버깅이 어렵고, 의도하지 않은 오버헤드를 유발합니다.


3. 생성자 호출의 주요 원인 6가지

1) 값 전달 방식 (by value) 사용

void foo(MyStruct obj);  // 복사 생성자 호출

2) 리턴 값의 복사

MyStruct bar() {
    MyStruct obj;
    return obj;  // 복사 또는 이동
}

3) 임시 객체 처리 (Temporary Object)

foo(MyStruct());  // 이동 생성자 또는 복사 생성자 호출

4) RVO 미적용 환경

일부 컴파일러에서는 Return Value Optimization이 적용되지 않아, 불필요한 복사가 발생합니다.

5) STL 컨테이너 사용 시 내부 복사

std::vector<MyStruct> vec;
vec.push_back(obj);  // 복사
vec.emplace_back(obj);  // C++11 이상에서는 이동 또는 직접 생성

6) 객체를 멤버로 갖는 클래스

클래스 A가 클래스 B의 객체를 멤버로 포함할 경우, B의 생성자가 암묵적으로 여러 번 호출될 수 있습니다.


4. 불필요한 생성자 호출을 줄이는 실전 팁

  • const MyStruct& 방식의 참조 전달 사용
  • C++11 이상에서는 **이동 생성자(move constructor)**를 정의
  • emplace_back() 사용 시 생성 위치를 지정하여 직접 생성
  • 복사 생성자, 이동 생성자, 소멸자에 로그 출력 코드 삽입하여 추적
  • 컴파일러 최적화 옵션 활성화 (예: -O2, -O3)
  • [[nodiscard]], explicit 키워드 등을 사용하여 의도 명확히 하기

5. 디버깅 기법: 생성자 호출 추적하는 법

클래스에 로그를 삽입하면 생성자 호출을 실시간으로 확인할 수 있습니다.

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의 디버거 등을 활용하면 메모리 및 객체 생성 흐름을 시각적으로 파악할 수 있습니다.


6. 마무리: 똑똑한 객체 관리가 코드 품질을 결정한다

C++의 생성자 호출은 단순히 객체를 만드는 작업이 아닙니다. 메모리, 성능, 논리 안정성과 직결된 핵심 요소입니다. 의도하지 않은 생성자 호출은 코드의 퍼포먼스를 갉아먹고, 디버깅을 어렵게 만들며, 숨겨진 버그의 원인이 될 수 있습니다.

지금 당신의 코드에서 생성자가 몇 번 호출되고 있는지, 직접 확인해보세요. 그리고 지금까지의 개발 습관을 점검해 보세요. 복잡한 코드를 만드는 건 컴파일러가 아니라 개발자 자신입니다.


참고자료