c++函数返回临时变量和局部变量,有什么区别?

2022-01-24 19:07:13 +08:00
 amiwrong123
#include <iostream>
using namespace std;

class A {
public:
    A() {
        cout << "default constructor" << endl;
    }

    A(const A& x) {
        cout << "copy constructor" << endl;
    }

    A(A&& x) {
        cout << "move constructor" << endl;
    }

    A& operator = (const A& x) {
        cout << "copy assigment operator" << endl;
        return *this;
    }

    A& operator = (A&& x) {
        cout << "move assigment operator" << endl;
        return *this;
    }

};

A returnValue() {
    return A();
}

A returnValue_1() {
    A a;
    return a;
}

A&& returnValue_2() {
    return A();
}

int main() {
    A e = returnValue();   // move constructor
    cout << endl;
    A e1 = returnValue_1();   // move constructor
    cout << endl;
    A e2 = returnValue_2();   // move constructor
    return 0;
}
/*
default constructor

default constructor
move constructor

default constructor
move constructor

A e = returnValue();只打印了一句,我理解执行A()打印了 default constructor ,然后 return 时经过 RVO 优化,就少打印一次。然后经过 https://en.cppreference.com/w/cpp/language/copy_initialization 里面提到的 纯右值的优化,又少打印一次。

A e2 = returnValue_2();打印两次是因为:执行A()打印了 default constructor ,然后 return 时经过 RVO 优化,就少打印一次。但由于函数返回值类型为 A&&,作为 亡值,就必须调用 移动构造函数。

但 A e1 = returnValue_1();为什么打印这两句阿?(它的返回值类型就是 A ,不是 A&&啊)

顺便问下,移动赋值操作符这里的提示是啥意思呀

(上面说的理解,如果有问题也请指正)

2392 次点击
所在节点    C++
10 条回复
nlzy
2022-01-24 19:23:22 +08:00
https://zh.cppreference.com/w/cpp/language/return

自动从局部变量和形参移动
lovelylain
2022-01-24 19:30:50 +08:00
returnValue_1 是 NRVO ,Named Return Value Optimization 具名返回值优化,简单说就是编译器会尽可能给你做返回值优化,减少拷贝。
amiwrong123
2022-01-24 21:09:37 +08:00
@jobmailcn #2
优化的话,不应该是连 移动构造函数 都不用调用了吗?(就只用打印一句了)
不过 从 复制构造函数 改成 移动构造函数,也是 进行优化了。
v2byy
2022-01-24 21:19:54 +08:00
@amiwrong123 使用 vs2019 release 模式下,确实 returnValue1 只调用了 default ctor
woodpenker
2022-01-24 21:33:28 +08:00
编译器无法确定一定能优化 NRVO 场景, 只能先放栈帧中再移出到调用方. 优化过的编译器识别到这种只有一个确定的命名返回值时就可以把 move 优化掉.
amiwrong123
2022-01-24 21:38:40 +08:00
@v2byy #4
真的哎,release 模式 居然不一样,这是为啥
ysc3839
2022-01-24 21:48:29 +08:00
@amiwrong123 因为 Debug 禁用了优化。
比如你这段代码,Release 下的优化可能直接把 cout << "default constructor" << endl; 内联到 main 里面了,调试时你可能想 step into returnValue_1(),结果发现直接“跳过”了这个函数,Debug 模式会禁用优化,便于调试。
statumer
2022-01-24 22:03:59 +08:00
建议你了解一下 copy elision
对哪些优化是强制性,哪些优化是非强制性的解释得很明确了。
现在你写的第一种是标准要求的 guaranteed copy elision 了,满足 copy elision 规则所以只调用默认构造函数(并不是两次优化)。
你写的第二种是非强制的 copy elision ,所以编译器可以自行决定是 RVO 还是 copy/move 。
你写的第三种应该是不合法的,A()是临时对象会被分配到栈上,returnValue_2 返回了一个栈上对象的引用。严重时会导致段错误。

https://en.cppreference.com/w/cpp/language/copy_elision
amiwrong123
2022-01-25 00:41:05 +08:00
@statumer #8
> 现在你写的第一种是标准要求的 guaranteed copy elision 了,满足 copy elision 规则所以只调用默认构造函数(并不是两次优化)。
那是不是也可以理解为 有两次 copy elision ,return 时一次,初始化变量时一次。

>你写的第三种应该是不合法的,A()是临时对象会被分配到栈上,returnValue_2 返回了一个栈上对象的引用。严重时会导致段错误。
我记得不是有一种,引用可以延长临时变量生命周期的东西?这样是不是就是合法的 了。

另外,看 cppreference 总感觉有些话看不懂:
>即使复制 /移动构造函数和析构函数拥有可观察的副作用
这句啥意思阿,啥叫可观察的副作用😂

struct C { /* ... */ };
C f();
struct D;
D g();
struct D : C {
D() : C(f()) { } // 初始化基类子对象时无消除
D(int) : D(g()) { } // 无消除,因为正在初始化的 D 对象可能是某个其他类的基类子对象
};
>无消除,因为正在初始化的 D 对象可能是某个其他类的基类子对象
它是说 D(g())这里吗,这里看起来是调用了一个 委托构造函数,正在初始化的 D 对象怎么可能是某个其他类的基类子对象呢?( D 也没有派生类阿。。)
agagega
2022-01-26 00:50:38 +08:00
@amiwrong123
可观察的副作用就是说有一个操作在那,限制了你没法做优化。举例:妈妈让小明下楼走一圈,小明可以出门坐一会然后给妈妈说自己走过了;但如果妈妈让小明下楼走一圈并买瓶酱油,小明就没有办法偷懒必须得下去了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/830339

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX