C++에서 기본 생성자, 복사 생성자, 이동 생성자, 복사 대입, 이동 대입, 소멸자 호출 시점 및 객체 소멸 순서

문제 상황을 명확히 보여주는 간단한 예제 코드를 살펴보자.

핵심 테스트 코드

int main()
{
    cout << "1."; Bar b1;         // 기본 생성자
    cout << "2."; Bar b2(b1);    // 복사 생성자(직접 초기화)
    cout << "3."; Bar b3 = b1;   // 복사 생성자(복사 초기화)
    cout << "4."; Bar b4 = static_cast<Bar>(10);  // 기본 최적화: 직접 생성; 최적화 비활성화 시 직접 생성+이동 생성(복사 초기화)
    cout << "5."; b1 = b2;       // 복사 대입
    cout << "6."; b1 = static_cast<Bar>(5);       // 직접 생성, 이동 대입, 우변 이동 후 객체 즉시 소멸
    cout << "----------" << endl;
}

실행 결과 및 분석

# g++ main.cpp && ./a.out
1.기본 생성 ptr = 0               # b1
2.복사 생성 ptr = 0x55a4e8cb0280  # b2
3.복사 생성 ptr = 0x55a4e8cb02a0  # b3
4.직접 생성 ptr = 0x55a4e8cb02c0  # b4
5.복사 대입 ptr = 0x55a4e8cb02f0  # b1=b2, deep copy로 새로운 ptr 주소 생성
6.직접 생성 ptr = 0x55a4e8cb0310  # static_cast<Bar>(5)
이동 대입 ptr = 0x55a4e8cb0310    # static_cast<Bar>(5) 가 b1으로 이전됨
소멸 ptr = 0                      # 이전 후 static_cast<Bar>(5) 소멸
----------
소멸 ptr = 0x55a4e8cb02c0       # b4
소멸 ptr = 0x55a4e8cb02a0       # b3
소멸 ptr = 0x55a4e8cb0280       # b2
소멸 ptr = 0x55a4e8cb0310       # b1

결론

  • 직접 초기화와 복사 초기화 모두복사 생성자를 호출한다.
  • 우측값(将亡值)인 테스트 코드의 static_cast<Bar>(5)는 즉시 소멸된다.
  • 지역 변수 소멸 순서(b4,b3,b2,b1)는 정의 순서(b1,b2,b3,b4)와 반대로 진행된다.
  • 기본 최적화 기능을 사용하면 생성+이동을 건너뛰고 임시 변수를 생략하여 b4 객체를 직접 생성할 수 있다. 즉, Bar b4 = static_cast<Bar>(10);는 기본적으로 Bar b4(10);으로 최적화된다. cppInsight에서도 동일한 결과를 확인할 수 있다: 기본 상태에서 string s1(3, 't');string s2 = string(3,'t');가 생성하는 코드가 완전히 동일하다 https://cppinsights.io/s/161fe366

완전한 테스트 코드

#include <iostream>
using namespace std;

class Bar {
  public:
    Bar() : len(0), data(nullptr) { cout << "기본 생성 ptr = " << data << endl; }
    explicit Bar(unsigned n) : len(n)
    {
        data = new int[len];
        cout << "직접 생성 ptr = " << data << endl;
    }
    Bar(const Bar& src) : len(src.len)
    {
        data = new int[len];
        for (unsigned i = 0; i < len; ++i) data[i] = src.data[i];
        cout << "복사 생성 ptr = " << data << endl;
    }
    Bar(Bar&& src) : len(src.len)
    {
        data = src.data;
        src.data = nullptr;
        cout << "이동 생성 ptr = " << data << endl;
    }
    Bar& operator=(const Bar& src)
    {  // 메모리 누수 발생! 표준 해결법: 복사 후 스왑
        if (this == &src) return *this;
        len = src.len;
        data = new int[len];
        for (unsigned i = 0; i < len; ++i) data[i] = src.data[i];
        cout << "복사 대입 ptr = " << data << endl;
        return *this;
    }
    Bar& operator=(Bar&& src)
    {
        if (this == &src) return *this;
        len = src.len;
        data = src.data;
        src.data = nullptr;
        cout << "이동 대입 ptr = " << data << endl;
        return *this;
    }
    ~Bar()
    {
        cout << "소멸 ptr = " << data << endl;
        if (data) {
            delete[] data;
            data = nullptr;
        }
    }

  private:
    unsigned len;
    int* data;
};

int main()
{
    cout << "1."; Bar b1;        // 기본 생성자
    cout << "2."; Bar b2(b1);   // 복사 생성자(직접 초기화)
    cout << "3."; Bar b3 = b1;  // 복사 생성자(복사 초기화)
    cout << "4."; Bar b4 = static_cast<Bar>(10);  // 기본 최적화: 직접 생성; 최적화 비활성화 시 직접 생성+이동 생성
    cout << "5."; b1 = b2;      // 복사 대입
    cout << "6."; b1 = static_cast<Bar>(5);       // 직접 생성, 이동 대입, 즉시 소멸
    cout << "----------" << endl;
}

태그: C++ constructors destructors copy-constructor move-constructor

8월 8일 23:58에 게시됨