THUOOP icon indicating copy to clipboard operation
THUOOP copied to clipboard

关于函数返回时是否调用移动构造函数的问题

Open NickLennonLiu opened this issue 3 years ago • 0 comments

有同学在做第三次作业第二题时注意到了这么一个现象,假设有函数foo如下:

Test foo(Test &a)
{
    return a;
}

其中Test类显式定义了移动构造函数,当我们执行

Test a;
Test b = foo(a);

时,foo函数返回的时候会调用移动构造函数还是拷贝构造函数呢?

L6-引用与复制一节的课件第41页讲到:

函数返回类对象(类中显式定义移动构造函数,不进⾏返回值优化): {return Test(); or return tmp;}均调用移动构造

然而程序运行结果表明,上述代码中函数返回时调用的是拷贝构造函数,而非同学所希望的移动构造函数。是因为a是左值吗?并没有那么简单,请观察如下代码:

Test foo(Test &a)
{
	Test c = a;
	return c; // Move constructor called.
}
Test foo(Test &a)
{
	Test &c = a;
	return c; // Copy constructor called.
}
Test foo()
{
	Test a;
	Test &c = a;
	return c; // Copy constructor called.
}

我们发现,如果返回的表达式是一个引用,那即便返回的类实现了移动构造函数,编译器仍然选择使用拷贝构造。

究其原因,函数返回时是否调用移动构造函数与返回表达式(returned expression)的生命周期有关,当返回的是一个局部变量时,编译器首先将它看做一个右值尝试进行移动,以减少不必要的构造/析构开销;而对于引用而言,无法确定它引用的对象在函数返回时是不是真的能被销毁,这时编译器才将返回值当做左值,通过拷贝构造函数进行返回。借用参考链接[1]中的回答,就是return隐式地尝试正确地利用移动构造函数来进行返回,当无法做到(如没有定义移动构造,返回的是引用等情况)时才选择拷贝构造。

虽然a为引用时,返回时调用了拷贝构造函数,但我们仍然可以使用std::move(a)来将表达式a类型转换为右值引用,进而调用移动构造函数。但需要知道的是,对一个引用a进行移动构造带来的结果绝非我们所希望的,这使得a所引用的对象不再可用,引用a本身也随之失去了意义。

参考链接:

[1] https://stackoverflow.com/questions/45336433/move-constructor-called-on-return-instead-of-copy (该问题描述与本文描述的问题正好相反,提问者认为函数返回将调用拷贝构造函数,而实际上调用了移动构造函数。问题下面的回答与本issue密切相关)

[2]https://en.cppreference.com/w/cpp/language/return#Notes cppreference关于函数返回的说明

NickLennonLiu avatar Apr 24 '21 14:04 NickLennonLiu