什么时候需要使用返回值优化?

4
我对返回值优化有些困惑,这里是一个例子。
#include <iostream>
using namespace std;
class A{
    int x;
    public:
        A(int val=0):x(val){
            cout << "A" << x << endl;
        }
        A(const A& a){
            x=a.x;
            cout << "B " << x << endl;
        }
        void SetX(int x){
            this -> x=x;
        }
        ~A(){
            cout << "D " << x << endl;
        }
};
A f(A a){
    cout << "C " << endl;
    a.SetX(100);
    return a;
}
int main(){
    A a(1);
    A b=f(a); // Why Copy constructor instead of RVO?
    b.SetX(-100);
    return 0;
}

输出

A 1 // ok
B 1 // ok
C // ok
B 100 // why it's here? Why copy constructor instead of RVO?
D 100 // why after the above line? it should be before the above line.
D -100 // ok 
D 1 // ok

我有点困惑B 100和D 100输出的含义。
1)为什么编译器会给出“B 100”输出,应该是RVO(不应该调用复制构造函数)。
2)第二个问题是,如果调用了复制构造函数,则“D 100”应该在“B 100”之前,因为在“fun()”函数中对象超出了范围,在“A b = f(a);”语句之前。
3个回答

2

首先,RVO是绝对不必要的。它是被允许的,这是一个很大的区别。

编辑。

我的初步答案在构造函数的顺序上是错误的。我错过了输出中的一行,实际上,这意味着复制遗漏了完全发生。在3次语义复制中(从f()到临时位置通过return a; 从临时位置到b=f(a)的右侧;从右侧到b),有1个没有被省略并且完全复制了。我不知道是哪一个。


通过按值传递到f的复制品并不是OP正在询问的那个。 OP在问第二个副本。 - Chris Drew
我已经编辑了我的问题,试图回答这个问题。现在有意义了吗? - SergeyA
@SergeyA 是的,您的意思是当调用语句结束时,fun()的对象会被销毁?也就是说,当A b=f(a);结束时,对象会被销毁? - user5028722
实际上,在此之前它已经被销毁了。可以这样想——编译器会在您的f()函数的}右边插入对析构函数的调用。 - SergeyA
@SergeyA 再次困惑,那么为什么在析构函数之前复制构造函数 B 100D 100 :( :( - user5028722
好的,我可能需要看一下。我误读了输出。我会很快更新我的答案。 - SergeyA

1

1) 为什么编译器会给B输出100,它应该是RVO(不应该调用复制构造函数)。

是的,应该调用,因为您将a作为参数获得了一个副本。

D 100

之后发生a超出作用域,所以会发生这种情况。


是的..那么为什么在B 100后还有D 100呢?如果return a超出了范围。 那么在此之后应该调用复制构造函数。你明白我的问题吗? - user5028722
@SergeyA:是的,那是个笔误。我正在用我的一半大脑泡咖啡。啊啊啊,咖啡...... - Marcus Müller
@MarcusMüller 最后一个问题是,当我从函数返回临时对象时,RVO会执行吗?就像这个例子:return A(100); 这意味着RVO仅适用于临时对象? - user5028722
正如@SergeyA所说,RVO并不一定会发生。 - Marcus Müller
@MarcusMüller 好的,您想阅读SergeyA答案上的最后两条评论吗?并且您能否解决我的困惑。 - user5028722
显示剩余3条评论

1
RVO或更具体地说,命名返回值优化(NRVO)是一种拷贝省略方式,在以下情况下允许使用[12.8/31]:在类返回类型的函数中的return语句中,当表达式是非易失性自动对象(除了函数或catch子句参数)的名称,并且该对象与函数返回类型具有相同的cv-unqualified类型时,可以通过直接构造自动对象到函数的返回值中来省略复制/移动操作[强调我]。因此,在这种情况下不允许使用RVO,因为a是一个函数参数。在函数结束之前,但在它为返回值取得a的副本之后,必须销毁临时变量a。

请注意,在C++03中允许这样做,但从C++11开始引入了此限制,而没有任何好的原因,除了它很难实现。 - Marc Glisse
@LetDoIt 有效地,是的。 - Chris Drew
@LetDoIt 对我来说不起作用。你是在运行发布版本吗? - Chris Drew
@ChrisDrew 我不知道发布版本,我只是使用CTRL+F5。最佳运行代码的方法是什么? - user5028722
如果你想让Visual Studio优化你的代码以便更快地运行,就需要点击顶部的Debug并将其改为Release。 - Chris Drew
显示剩余4条评论

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