复制省略和移动语义未按预期工作

3

我制作了一个程序来评估这样做的性能差异:

func3(func2(func1()));

与此相比:

retval1 = func1();
retval2 = func2(retval1);
func3(retval2);

我更喜欢后者,因为它更易读和调试。我想知道编译器(MSVC 12.0)是否会在发布版本中优化掉中间对象。我的测试程序如下:

#include <iostream>

using namespace std;

struct Indicator {
    Indicator() {cout << "Default constructor" << endl;}
    Indicator(const Indicator& other) {cout << "Copy constructor" << endl;}
    const Indicator& operator=(const Indicator& other) {cout << "Assignment operator" << endl;}
    ~Indicator() {cout << "Destructor" << endl;}
};

Indicator func1()
{return Indicator();}

Indicator func2(Indicator&& i)
{return std::move(i);}

Indicator func3(Indicator&& i)
{return std::move(i);}

int main() {
    Indicator i = func3(func2(func1()));
    cout << &i << endl;
    return 0;
}

我很惊讶地发现即使使用了 -O2,仍然会创建三个 Indicator 实例:

Default constructor
Copy constructor
Copy constructor
Destructor
Destructor
00000000002EFC70
Destructor
Press <RETURN> to close this window...

这与我的移动语义理解相冲突,移动语义是指在这种情况下应该只创建一个Indicator实例。我还以为编译器应该能够对链接的函数调用使用NRVO。有人可以向我解释一下这里发生了什么吗?
1个回答

6

根据5条规则,当您定义副本构造函数和副本赋值运算符时,您将禁用编译器生成的移动构造函数和移动赋值运算符。

如果您定义移动构造函数,则会得到所期望的输出。

Indicator(Indicator&& other) {cout << "Move constructor" << endl;}
Indicator& operator=(Indicator&& other) {cout << "Move assignment operator" << endl;}

工作演示


1
值得注意的是,Visual C++ 12根本不生成隐式移动构造函数。 - stgatilov
啊。是的,我明白了。我刚习惯了遵守三个规则,现在还要记得定义移动构造函数... - Carlton
1
@Carlton 或者遵循零规则!如果你不必定义其中一个(或一些),那么就让编译器为你创建它们! - Cory Kramer

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