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

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

  •  
  •   amiwrong123 · 2022-01-24 19:07:13 +08:00 · 2596 次点击
    这是一个创建于 1038 天前的主题,其中的信息可能已经有所发展或是发生改变。
    #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&&啊)

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

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

    10 条回复    2022-01-26 00:50:38 +08:00
    nlzy
        1
    nlzy  
       2022-01-24 19:23:22 +08:00 via Android
    https://zh.cppreference.com/w/cpp/language/return

    自动从局部变量和形参移动
    lovelylain
        2
    lovelylain  
       2022-01-24 19:30:50 +08:00
    returnValue_1 是 NRVO ,Named Return Value Optimization 具名返回值优化,简单说就是编译器会尽可能给你做返回值优化,减少拷贝。
    amiwrong123
        3
    amiwrong123  
    OP
       2022-01-24 21:09:37 +08:00
    @jobmailcn #2
    优化的话,不应该是连 移动构造函数 都不用调用了吗?(就只用打印一句了)
    不过 从 复制构造函数 改成 移动构造函数,也是 进行优化了。
    v2byy
        4
    v2byy  
       2022-01-24 21:19:54 +08:00
    @amiwrong123 使用 vs2019 release 模式下,确实 returnValue1 只调用了 default ctor
    woodpenker
        5
    woodpenker  
       2022-01-24 21:33:28 +08:00
    编译器无法确定一定能优化 NRVO 场景, 只能先放栈帧中再移出到调用方. 优化过的编译器识别到这种只有一个确定的命名返回值时就可以把 move 优化掉.
    amiwrong123
        6
    amiwrong123  
    OP
       2022-01-24 21:38:40 +08:00
    @v2byy #4
    真的哎,release 模式 居然不一样,这是为啥
    ysc3839
        7
    ysc3839  
       2022-01-24 21:48:29 +08:00 via Android
    @amiwrong123 因为 Debug 禁用了优化。
    比如你这段代码,Release 下的优化可能直接把 cout << "default constructor" << endl; 内联到 main 里面了,调试时你可能想 step into returnValue_1(),结果发现直接“跳过”了这个函数,Debug 模式会禁用优化,便于调试。
    statumer
        8
    statumer  
       2022-01-24 22:03:59 +08:00   ❤️ 2
    建议你了解一下 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
        9
    amiwrong123  
    OP
       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
        10
    agagega  
       2022-01-26 00:50:38 +08:00
    @amiwrong123
    可观察的副作用就是说有一个操作在那,限制了你没法做优化。举例:妈妈让小明下楼走一圈,小明可以出门坐一会然后给妈妈说自己走过了;但如果妈妈让小明下楼走一圈并买瓶酱油,小明就没有办法偷懒必须得下去了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1384 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 17:37 · PVG 01:37 · LAX 09:37 · JFK 12:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.