std::move和对象的构造/析构

3

据我记得,在调用任何函数之前,它会在堆栈中为函数结果和参数分配内存。

这是否意味着如果我有

T func()
{
    T a;
    return std::move(a);
}

我将仍然需要复制,因为已经为整个T分配了内存? 我还看到类似问题的解答中

的内容。
return a; 

是相同的。
return std::move(a);

那么,我无法避免将内容复制到堆栈中吗?rvalue是堆栈中的值吗?

在某些地方使用它是否是一个好方法:

T a = std::move(func());

那么我应该避免复制函数的结果吗?我是否仍需要创建特殊的移动构造函数和移动操作符=?

我尝试测试它并获得了以下结果:

class Temp
{
public:
    Temp()
    {
        cout << "construct" << endl;
        i = 5;
    }
    ~Temp()
    {
        cout << "destruct" << endl;
    }
    Temp(const Temp& t)
    {
        i = t.i;
        cout << "copy construct" << endl;
    }
    Temp operator=(const Temp& t)
    {
        i = t.i;
        cout << "operator =" << endl;
        return *this;
    }
    int i;

};

Temp tempfunc1()
{
    Temp t1;
    t1.i = 7;
    return t1;
}

Temp tempfunc2()
{
    Temp t1;
    t1.i = 8;
    return std::move(t1);
}

int main()
{
        Temp t1;
        Temp t2;
        t2.i = 6;
        t1 = t2;
        cout << t1.i << endl;
        t1.i = 5;
        t1 = std::move(t2);
        cout << t1.i << endl;
        cout << "NEXT" << endl;
        t1 = tempfunc1();
        cout << t1.i << endl;
        cout << "NEXT" << endl;
        t1 = std::move(tempfunc1());
        cout << t1.i << endl;
        cout << "NEXT" << endl;
        t1 = tempfunc2();
        cout << t1.i << endl;
        cout << "NEXT" << endl;
        t1 = std::move(tempfunc2());
        cout << t1.i << endl;
}

结果如下:

construct
construct
operator =
copy construct
destruct
6
operator =
copy construct
destruct
6
NEXT
construct
copy construct
destruct
operator =
copy construct
destruct
destruct
7
NEXT
construct
copy construct
destruct
operator =
copy construct
destruct
destruct
7
NEXT
construct
copy construct
destruct
operator =
copy construct
destruct
destruct
8
NEXT
construct
copy construct
destruct
operator =
copy construct
destruct
destruct
8

是否使用std::move并不影响任何东西。或者说它只在存在特殊构造函数和析构函数时才有效?

为什么"t1 = t2"会调用拷贝构造函数和operator=函数两个方法?

非常抱歉提了这么多可能很简单的问题,即使我已经看了很多相关内容,也可能是因为我的英语不好,还需要一些解释。

提前感谢您的帮助。


为什么“t1 = t2”会调用复制构造函数和赋值运算符?因为首先你调用operator=来获取要复制的对象,然后再进行复制构造。 - Theolodis
@DDrmmr,我特意没有使用参考,因为有了它们就不需要移动对象了。 - Arkady
你从未为你的类提供任何接受“Temp &&”的构造函数或赋值运算符,因此肯定会调用“Temp const&”版本。而你的“operator =”返回一个“Temp”(按值),因此肯定会有一个构造函数调用来创建该返回值。你应该缩小问题的范围,因为现在这里有相当多的问题。 - Mankarse
所以,std::move和移动语义并没有什么神奇的地方,我只是从编译器中得到了工具,以便不再创建自己的函数来调用它,例如之前的“移动函数”,但现在我有了特殊的构造函数和函数(std::move)来调用它?如果我没有编写特殊的Test&&构造函数,std::move就无法工作? - Arkady
@Arkady std::move 的作用只是让左值看起来像右值(这应该暗示你在示例中不需要使用它)。因此,如果您想要特殊的右值引用行为,则需要一个右值引用重载。 - juanchopanza
显示剩余7条评论
1个回答

3

我已经对你的代码进行了一些更改,这些更改可能有助于你理解和探索所有这些工作的原理。我为每个Temp对象添加了一个id元素,以便更容易地理解哪个对象是哪个,并将operator=的签名更改为返回引用而不是对象。首先,这里是将其变成完整程序所需的include

#include <iostream>
using std::cout;
using std::endl;

下面是现在包含一个带有 &&std::move 构造函数和一个 move = 运算符的类:
class Temp
{
    int id;
public:
    Temp() : id(++serial), i(5)
    {
        cout << "construct " << id << endl;
    }
    ~Temp()
    {
        cout << "destruct " << id << endl;
    }
    Temp(const Temp& t) : id(++serial), i(t.i)
    {
        cout << "copy construct " << id << " from " << t.id << endl;
    }
    Temp(Temp &&t) : id(++serial), i(t.i)
    {
        t.i = 5;  // set source to a default state
        cout << "move construct " << id << " from " << t.id << endl;
    }
    Temp &operator=(const Temp& t)
    {
        i = t.i;
        cout << "operator = " << id << " from " << t.id << endl;
        return *this;
    }
    Temp &operator=(Temp&& t)
    {
        i = t.i;
        t.i = 5;  // set source to a default state
        cout << "move operator = " << id << " from " << t.id << endl;
        return *this;
    }
    int i;
    static int serial;
};


int Temp::serial = 0;

您的函数仍然是相同的,但请注意评论。
Temp tempfunc1()
{
    Temp t1;
    t1.i = 7;
    return t1;
}

Temp tempfunc2()
{
    Temp t1;
    t1.i = 8;
    return std::move(t1);   // not necessary to call std::move here
}

我稍微修改了main()函数以展示这个工作原理:

int main()
{
    Temp t1;   
    Temp t2;   
    t2.i = 6;   
    t1 = t2;   
    cout << t1.i << endl;
    t1.i = 5;
    t1 = t2;
    cout << t1.i << endl;
    cout << "NEXT" << endl;
    t1 = tempfunc1();
    cout << t1.i << endl;
    cout << "NEXT" << endl;
    t1 = std::move(tempfunc1());
    cout << t1.i << endl;
    cout << "NEXT" << endl;
    t1 = tempfunc2();
    cout << t1.i << endl;
    cout << "NEXT" << endl;
    Temp t3(tempfunc1());
    cout << t3.i << endl;
    cout << "NEXT" << endl;
    Temp t4(t1);
    cout << t4.i << endl;
}

最后,这里是输出结果:

construct 1
construct 2
operator = 1 from 2
6
operator = 1 from 2
6
NEXT
construct 3
move operator = 1 from 3
destruct 3
7
NEXT
construct 4
move operator = 1 from 4
destruct 4
7
NEXT
construct 5
move construct 6 from 5
destruct 5
move operator = 1 from 6
destruct 6
8
NEXT
construct 7
7
NEXT
copy construct 8 from 1
8
destruct 8
destruct 7
destruct 2
destruct 1

正如您所看到的,使用固定的operator=时,不会创建临时对象。而一旦提供了operator=的移动版本,临时对象(例如由tempfunc1()tempfunc2()函数返回的那些对象)将自动使用移动语义。您的tempfunc2()实际上并不需要其中的std :: move调用。正如您所看到的,这只会创建另一个临时对象,因此它更多的是帮倒忙。最后请注意,在创建t3时,只创建了一个对象,不需要临时构造函数或move构造函数。
更新
值得注意的是,在这个简单的类中,移动构造函数并没有什么帮助,但对于使用分配内存或计算昂贵的类来说,它可以帮助很多。在这些情况下,需要记住移动构造函数(或operator=的移动版本)需要多个步骤。具体而言,您必须:
1.释放目标对象使用的任何资源,然后 2.将源对象的资源移动到目标对象中,然后 3.设置源对象状态,使得析构函数可以被调用(例如,如果该类有分配的内存,则应将指针设置为nullptr,以便析构函数可以正确操作),最后, 4.返回*this

t1 = std::move(tempfunc1()); 和 t1 = tempfunc1();(在第3和第4个对象处)都具有相同的移动语义!为什么简单的 t1 = tempfunc1() 被称为移动操作符? - Arkady
@Arkady:因为编译器“知道”临时对象即将被删除,所以它使用移动版本的operator=作为一种简单的优化。 - Edward

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接