V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
woshichuanqilz
V2EX  ›  程序员

Cpp 移动构造函数的问题

  •  
  •   woshichuanqilz · 2022-06-19 22:37:48 +08:00 · 1499 次点击
    这是一个创建于 889 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这个是我的代码, 按照移动构造函数的定义, 我先创建了一个对象 a, 这个对象强制使用 move 调用移动构造函数赋值给 b, 这个时候, a, b 里面的成员变量的地址 data 应该是一样的, 为什么我这里显示的不一样?

    运行结果

    Constructor is called for 10 000000950D2FF7F8 // addr Move Constructor for 10 000000950D2FF818 // addr Destructor is called for 10 Destructor is called for nullptr

    // C++ program with declaring the
    // move constructor
    #include <iostream>
    #include <vector>
    using namespace std;
    
    // Move Class
    class Move {
    
    public:
    
        int* data;
        // Constructor
        Move(int d)
        {
            // Declare object in the heap
            data = new int;
            *data = d;
            cout << "Constructor is called for "
                << d << endl;
        };
    
        // Copy Constructor
        Move(const Move& source)
            : Move{ *source.data }
        {
    
            // Copying the data by making
            // deep copy
            cout << "Copy Constructor is called -"
                << "Deep copy for "
                << *source.data
                << endl;
        }
    
        // Move Constructor
        Move(Move&& source)
            : data{ source.data }
        {
    
            cout << "Move Constructor for "
                << *source.data << endl;
            source.data = nullptr;
        }
    
    
        // Destructor
        ~Move()
        {
            if (data != nullptr)
    
                // If pointer is not pointing
                // to nullptr
                cout << "Destructor is called for "
                << *data << endl;
            else
    
                // If pointer is pointing
                // to nullptr
                cout << "Destructor is called"
                << " for nullptr "
                << endl;
    
            // Free up the memory assigned to
            // The data member of the object
            delete data;
        }
    };
    
    // Driver Code
    int main()
    {
        // Vector of Move Class
        //vector<Move> vec;
    
        //// Inserting Object of Move Class
        //vec.push_back(Move{ 10 });
        //vec.push_back(Move{ 20 });
        Move a(10);
        cout << &(a.data) << endl;
        Move b = move(a);
        cout << &(b.data) << endl;
        return 0;
    }
    
    14 条回复    2022-06-20 02:30:23 +08:00
    codehz
        1
    codehz  
       2022-06-19 22:43:24 +08:00
    移动构造没有移动对象本体——移动的是内部引用的其他资源
    (然后如果用 vector 的话,可以 vector<Move> vec; vec.emplace(10); 来直接原地构造到对应位置去
    codehz
        2
    codehz  
       2022-06-19 22:44:46 +08:00
    打错,是 emplace_back
    stein42
        3
    stein42  
       2022-06-19 22:45:46 +08:00
    相同的应该是 a.data 和 b.data ,它们的类型是 int*。
    而不是 &(a.data) 和 &(b.data),它们的类型是 int**。
    wevsty
        4
    wevsty  
       2022-06-19 22:49:50 +08:00
    &(a.data) 代表的是 a 这个对象中的 data 成员所在的地址。
    所以为什么 &(a.data) 要等于 &(b.data)?

    移动构造并不是把原先的对象取一个不同名字的引用,你实现的移动构造函数只能保证 a.data = b.data 但是 a 和 b 仍然是两个独立的对象。
    woshichuanqilz
        5
    woshichuanqilz  
    OP
       2022-06-19 23:02:16 +08:00
    @wevsty 那移动的意义在哪 这不和拷贝构造函数一样了吗?
    woshichuanqilz
        6
    woshichuanqilz  
    OP
       2022-06-19 23:03:45 +08:00
    @codehz 这个我理解是移动构造函数最常见的情况, 就是 move 一个临时变量, 但是如果我强制调用 move 在一个声明的变量上给另一个, 似乎没有出现移动构造的效果
    woshichuanqilz
        7
    woshichuanqilz  
    OP
       2022-06-19 23:05:45 +08:00
    @stein42 如果地址不相同的话说明复制了一份? 那么为什么叫移动, 不是和拷贝构造一样了
    codehz
        8
    codehz  
       2022-06-19 23:10:25 +08:00
    @woshichuanqilz
    移动的意义就是可以表达不“深层次”复制内容的意图,而是转移原始对象的对引用资源到新的对象上(这个也得你自己实现,比如把原始对象里对应指针属性设置为 0 ,free 的时候就不会 double free 了)
    stein42
        9
    stein42  
       2022-06-19 23:16:29 +08:00
    C++ 是把对象和资源关联到一起,移动是移动关联的资源。
    这里 Move 对象关联的资源就是 new 分配的内存,析构函数会释放这块内存(如果不为空的话)。
    复制构造函数是新分配了一块内存,并把值复制过来。
    移动构造函数是转移了资源,这里既 a 分配的内存转移给了 b 。
    yanqiyu
        10
    yanqiyu  
       2022-06-19 23:23:27 +08:00   ❤️ 1
    @woshichuanqilz 移动的意思是 *data 这个对象没必要被复制(重新分配空间,data = new int 以及赋值),并且原先的类失去所有权。
    data 是个类成员,在不同类里面肯定是不同的东西,占据不同的地址
    wevsty
        11
    wevsty  
       2022-06-19 23:24:19 +08:00   ❤️ 1
    @woshichuanqilz

    通常约定,拷贝构造是对对象做深拷贝,移动构造尽量做移动(浅拷贝)。
    对于基本数据类型( int ,float 等)深拷贝和浅拷贝是一样的。
    但是对于指针类型,浅拷贝通常只复制指针保存的地址。光拷贝指针中保存的地址显然不能保证不会产生资源的冲突,所以如果做深拷贝需要重新分配一块空间再对指针中指向的数据进行再次拷贝。

    另外移动构造还有转移所有权的含义,移动之后原对象应该被视为已经废弃的对象(或者说没有管理任何资源的对象)。
    yanqiyu
        12
    yanqiyu  
       2022-06-19 23:24:39 +08:00
    要是 *data 这个对象复制开销很大,或者根本就不能复制,作用就来了
    sora2blue
        13
    sora2blue  
       2022-06-20 00:47:02 +08:00
    可不可以这样假设:类在底层实现的时候,对内置类型的成员直接存储,对不是内置类型的成员都只存储指针;内置类型如指针的拷贝花费较少不计入,在移动构造时对这两者浅拷贝即可。相对地,在拷贝构造时对内置成员一样拷贝,对不是内置类型的成员另外拷贝一遍指针指向的成员对象,然后存储这个新的指针值在底层,而这个就是移动构造的优化对象。
    所以对类的成员取地址,取到的地址都是类底层实现时内置类型成员或非内置类型成员指针的地址,比如:OP 的例子里,指针都是内置类型,直接复制即可,a.data 和 b.data 的存储位置在底层仍然是不一样的;把 data 的类型换成结构体或者类,结果是一样的,在底层指向结构体和类的指针的存储位置仍然是不一样的。
    Buges
        14
    Buges  
       2022-06-20 02:30:23 +08:00 via Android
    move 对栈上的对象来说就是 copy ,对堆上的对象来说是 copy 指针。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3228 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 13:07 · PVG 21:07 · LAX 05:07 · JFK 08:07
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.