在 C++14 中关闭 RVO/NRVO 时,如何返回对象?

When in C++14 with RVO/NRVO closed, how is the object returned?

提问人:Isuxiz Slidder 提问时间:5/21/2023 更新时间:5/21/2023 访问量:100

问:

我正在学习移动语义,所以我写了一个小程序,如下所示:

#include <iostream>

using namespace std;

int one_int = 123;

class A {
public:
    int *a;

    A(int *ptr) : a(ptr) {
        cout << "In A's constructor..." << endl;
    }

    A(const A &other) {
        cout << "In A's copy constructor..." << endl;
        a = other.a;
    }

    A(A &&other) noexcept {
        cout << "In A's move constructor..." << endl;
        a = other.a;
        other.a = nullptr;
    }

    ~A() {
        cout << "In A's destructor..." << endl;
    }
};

A make_obj() {
    cout << "In make_obj..." << endl;
    return A(&one_int);
}

A make_obj_by_move(A &&source) {
    cout << "In make_obj_by_move..." << endl;
    return A(static_cast<A&&>(source));
}

A make_obj_by_copy(A source) {
    cout << "In make_obj_by_copy..." << endl;
    return A(source);
}

int main() {
    A obj2 = make_obj_by_move(make_obj());
    cout << endl;
    A obj1 = make_obj_by_copy(make_obj());
    cout << endl;
    // to ensure that the information printed when obj1 and obj2 are destroyed is separated from the above ones
    obj1.a = nullptr;
    obj2.a = nullptr;
    return 0;
}

我通过编译器选项关闭了 clang 的 RVO/NRVO 并使用 .我得到了以下输出,非常令人困惑:-fno-elide-constructors-std=c++14

In make_obj...                                
In A's constructor...                   // make tmp obj
In A's move constructor...              // w/o RVO, need another construction to get return value?
In A's destructor...                    // destory the temp obj in make_obj scope?                
In make_obj_by_move...                
In A's move constructor...              // call the move constructor like expected? I guess by the position of "copy constructor" below
In A's move constructor...              // what happened here ???
In A's destructor...                    // destory which?            
In A's move constructor...              // w/o NRVO, need another construction to get obj1?
In A's destructor...                    // destory which?
In A's destructor...                    // destory which?

In make_obj...                            
In A's constructor...                   // make tmp obj
In A's move constructor...              // w/o RVO, need another construction to get return value?
In A's destructor...                    // destory the temp obj in make_obj scope?            
In A's move constructor...              // what happened here ???
In make_obj_by_copy...                
In A's copy constructor...              // call the copy constructor like expected
In A's move constructor...              // what happened here ???
In A's destructor...                    // destory which?            
In A's move constructor...              // w/o NRVO, need another construction to get obj2?
In A's destructor...                    // destory which?
In A's destructor...                    // destory which?
In A's destructor...                    // destory which?            

In A's destructor...                    // destory obj1/obj2
In A's destructor...                    // destory obj2/obj1

这里到底发生了什么?为什么会召唤许多意想不到的移动构造和破坏?我试图解释一些输出,但仍然有很多我不明白的输出行。你可以帮我吗?谢谢。

C++ C++14 返回值优化 NRVO

评论

2赞 Nathan Pierson 5/21/2023
尝试在构造函数和析构函数中打印,以便更轻松地匹配哪些析构函数对应于哪些构造函数。this
1赞 PaulMcKenzie 5/21/2023
// destory which?-- 如前所述,您知道哪个对象正在被打印销毁。通常,您应该始终在构造函数和析构函数中打印,这样您就不会对正在创建和销毁的内容感到困惑。现在,输出是我们任何人都能快速看到真正发生的事情的唯一途径。thisthisthis

答:

1赞 Miles Budnek 5/21/2023 #1

在没有任何复制省略的情况下,通常有三个对象从函数返回:

  1. 传递给关键字的对象return
  2. 函数的返回值
  3. 由返回值初始化的对象
  • 您为关键字提供的对象(通常)是函数本地对象,并且它的存储位于函数的堆栈框架中。return

  • 函数的返回值存在于函数的作用域之外,作为临时对象存在于调用方的堆栈帧中,该临时对象仅存在于调用函数的完整表达式结束之前。

  • 由返回值初始化的对象也存在于调用方的堆栈帧中,并且(通常)在调用方中具有名称和更广泛的范围

复制省略允许编译器在正确的情况下将所有这三个对象折叠为一个对象。


我已经注释了您的程序的输出,以准确显示每行正在谈论的对象:

In make_obj...                                
In A's constructor...         // create function-local temporary object
In A's move constructor...    // construct make_obj's return value by move from the function local temp object
In A's destructor...          // destroy the function-local temp object
In make_obj_by_move...
In A's move constructor...    // create function-local temporary object
In A's move constructor...    // construct make_obj_by_move's return value by move from the function-local temp object
In A's destructor...          // destroy the function-local temp object
In A's move constructor...    // construct obj1 in main by move from make_obj_by_move's return object
In A's destructor...          // destroy make_obj_by_move's return object
In A's destructor...          // destroy make_obj's return object

In make_obj...
In A's constructor...         // create function-local temporary object
In A's move constructor...    // construct make_obj's return value by move from the function local temp object
In A's destructor...          // destroy the function-local temp object
In A's move constructor...    // construct make_obj_by_copy's source parameter by move from make_obj's return object
In make_obj_by_copy...                
In A's copy constructor...    // create function-local temp object by copy from the source parameter
In A's move constructor...    // construct make_obj_by_copy's return value by move from the function-local temp object
In A's destructor...          // destroy the function-local temp object
In A's move constructor...    // construct obj2 in main by move from make_obj_by_move's return object
In A's destructor...          // destroy make_obj_by_copy's return object
In A's destructor...          // destroy make_obj_by_copy's source parameter
In A's destructor...          // destroy make_obj's return object

In A's destructor...          // destroy obj2
In A's destructor...          // destroy obj1

顺便说一句,在这些情况下,在每个检测操作中打印指针的值会很有用,这样你就可以很容易地准确地看到操作是在哪个对象上执行的。this