C++ 何时调用默认构造、拷贝构造、移动构造、拷贝赋值、移动赋值、析构以及对象析构顺序

一小段代码足以说明问题

核心测试代码

int main()
{
    cout << "1."; Foo a1;       // 默认构造
    cout << "2."; Foo a2(a1);   // 拷贝构造(直接初始化)
    cout << "3."; Foo a3 = a1;  // 拷贝构造(拷贝初始化)
    cout << "4."; Foo a4 = static_cast<Foo>(10);  // 默认优化:直接构造;如果禁用一切优化,直接构造+移动构造(拷贝初始化)
    cout << "5."; a1 = a2;      // 拷贝赋值
    cout << "6."; a1 = static_cast<Foo>(5);       // 直接构造、移动赋值、立即析构右侧移动后的对象
    cout << "----------" << endl;
}

执行结果及分析

# g++ main.cpp && ./a.out
1.默认构造 ptr = 0               # a1
2.拷贝构造 ptr = 0x559e05fe0280  # a2
3.拷贝构造 ptr = 0x559e05fe02a0  # a3
4.直接构造 ptr = 0x559e05fe02c0  # a4
5.拷贝赋值 ptr = 0x559e05fe02f0  # a1=a2,深拷贝 a2 导致生成新的 ptr 地址
6.直接构造 ptr = 0x559e05fe0310  # static_cast<Foo>(5)
移动赋值 ptr = 0x559e05fe0310    # static_cast<Foo>(5) 转移给了 a1
析构 ptr = 0                    # 转移后 static_cast<Foo>(5) 的析构
----------
析构 ptr = 0x559e05fe02c0       # a4
析构 ptr = 0x559e05fe02a0       # a3
析构 ptr = 0x559e05fe0280       # a2
析构 ptr = 0x559e05fe0310       # a1

结论

  • 直接初始化和拷贝初始化都调用拷贝构造
  • 右值(将亡值)如测试代码中的 static_cast<Foo>(5),立即析构
  • 局部变量析构顺序(a4,a3,a2,a1)和定义顺序(a1,a2,a3,a4)相反
  • 默认优化,可以跳过构造+移动,省略临时变量,直接构造 a4 对象。即 Foo a4 = static_cast<Foo>(10); 默认直接优化为 Foo a4(10);。通过 cppInsight 也能得到相同结论:默认情况下,string s1(3, 't');string s2 = string(3,'t'); 生成的代码完全相同 https://cppinsights.io/s/161fe366

完整测试代码

#include <iostream>
using namespace std;

class Foo {
  public:
    Foo() : size(0), ptr(nullptr) { cout << "默认构造 ptr = " << ptr << endl; }
    explicit Foo(unsigned s) : size(s)
    {
        ptr = new int[size];
        cout << "直接构造 ptr = " << ptr << endl;
    }
    Foo(const Foo &I) : size(I.size)
    {
        ptr = new int[size];
        for (unsigned i = 0; i < size; ++i) ptr[i] = I.ptr[i];
        cout << "拷贝构造 ptr = " << ptr << endl;
    }
    Foo(Foo &&I) : size(I.size)
    {
        ptr = I.ptr;
        I.ptr = nullptr;
        cout << "移动构造 ptr = " << ptr << endl;
    }
    Foo &operator=(const Foo &I)
    {  // 内存泄漏!标准解法:拷贝、交换
        if (this == &I) return *this;
        size = I.size;
        ptr = new int[size];
        for (unsigned i = 0; i < size; ++i) ptr[i] = I.ptr[i];
        cout << "拷贝赋值 ptr = " << ptr << endl;
        return *this;
    }
    Foo &operator=(Foo &&I)
    {
        if (this == &I) return *this;
        size = I.size;
        ptr = I.ptr;
        I.ptr = nullptr;
        cout << "移动赋值 ptr = " << ptr << endl;
        return *this;
    }
    ~Foo()
    {
        cout << "析构 ptr = " << ptr << endl;
        if (ptr) {
            delete[] ptr;
            ptr = nullptr;
        }
    }

  private:
    unsigned size;
    int *ptr;
};

int main()
{
    cout << "1."; Foo a1;        // 默认构造
    cout << "2."; Foo a2(a1);   // 拷贝构造(直接初始化)
    cout << "3."; Foo a3 = a1;  // 拷贝构造(拷贝初始化)
    cout << "4."; Foo a4 = static_cast<Foo>(10);  // 默认优化:直接构造;如果禁用一切优化,直接构造+移动构造(拷贝初始化)
    cout << "5."; a1 = a2;      // 拷贝赋值
    cout << "6."; a1 = static_cast<Foo>(5);       // 直接构造、移动赋值、立即析构右侧移动后的对象
    cout << "----------" << endl;
}
posted @ 2022-04-24 20:18  Zijian/TENG  阅读(338)  评论(0编辑  收藏  举报