谁复制了函数的返回值?

Ral*_*zky 8 c++ copying scopeguard c++11

是调用者还是被调用者复制或移动函数的返回值?例如,如果我想实现队列的pop()函数,就像这样

template <typename T> 
class queue
{
    std::deque<T> d;
public:
    // ... //
    T pop()
    {
        // Creates a variable whose destructor removes the first
        // element of the queue if no exception is thrown. 
        auto guard = ScopeSuccessGuard( [=]{ d.pop_front(); } );
        return d.front();
    }
}
Run Code Online (Sandbox Code Playgroud)

是复制前面元素后调用的范围内容的析构函数?

编辑:后续问题:行

auto item = q.pop();
Run Code Online (Sandbox Code Playgroud)

现在是非常安全的吗?

Ton*_*roy 9

在局部变量超出范围之前复制出返回值.复制/移动可能是临时位置(堆栈或寄存器)或直接到调用者自己的缓冲区或首选寄存器 - 这是优化/内联问题.

如果涉及的临时位置编译器必须在调用者和被调用者之间安排一些工作分工,并且对于返回值(当然还有函数参数)有许多OS和二进制对象/可执行文件格式的约定,这样的用一个编译器编译的库/对象通常仍然可以与另一个一起使用.

这条线路......

auto item = q.pop();
Run Code Online (Sandbox Code Playgroud)

...强烈的异常安全吗?

假设pop_front()不能throw,有趣的情况是返回一个临时位置,在返回函数后,该值再次被复制到调用缓冲区.在我看来,你没有充分保护这一点.Elision(被调用者直接在调用者的结果缓冲区/寄存器中构造返回值)是允许的但不是必需的.

为了探索这个,我编写了以下代码:

#include <iostream>

struct X
{
    X() { std::cout << "X::X(this " << (void*)this << ")\n"; }
    X(const X& rhs) { std::cout << "X::X(const X&, " << (void*)&rhs
                                << ", this " << (void*)this << ")\n"; }
    ~X() { std::cout << "X::~X(this " << (void*)this << ")\n"; }

    X& operator=(const X& rhs)
    { std::cout << "X::operator=(const X& " << (void*)&rhs
                << ", this " << (void*)this << ")\n"; return *this; }
};

struct Y
{
    Y() { std::cout << "Y::Y(this " << (void*)this << ")\n"; }
    ~Y() { std::cout << "Y::~Y(this " << (void*)this << ")\n"; }
};

X f()
{
   Y y;
   std::cout << "f() creating an X...\n";
   X x;
   std::cout << "f() return x...\n";
   return x;
};

int main()
{
    std::cout << "creating X in main...\n";
    X x;
    std::cout << "x = f(); main...\n";
    x = f();
}
Run Code Online (Sandbox Code Playgroud)

编译g++ -fno-elide-constructors,我的输出(附加注释)是:

creating X in main...
X::X(this 0x22cd50)
x = f(); main...
Y::Y(this 0x22cc90)
f() creating an X...
X::X(this 0x22cc80)
f() return x...
X::X(const X&, 0x22cc80, this 0x22cd40)   // copy-construct temporary
X::~X(this 0x22cc80)   // f-local x leaves scope
Y::~Y(this 0x22cc90)
X::operator=(const X& 0x22cd40, this 0x22cd50)  // from temporary to main's x
X::~X(this 0x22cd40)
X::~X(this 0x22cd50)
Run Code Online (Sandbox Code Playgroud)

显然,任务发生在f()左范围之后:任何例外情况都是在你的范围守卫(这里用Y代表)被摧毁之后.

如果main包含X x = f();or X x(f());,则会发生同样的事情,除了它是在销毁f()-local变量之后调用的复制构造函数.

(我很欣赏一个编译器的行为有时是一个很差的基础,无法判断标准是否需要工作,但是反过来它更可靠:当它不能正常工作时编译器被破坏 - 这是相对罕见的 - 或者标准不要求它.这里,编译器行为只是用来增加我对标准要求的印象.)

奇怪的细节:并不是说通常只能用一种方式调用代码才有用,但是可能安全的东西是const X& x = f();,因为const引用延长了临时代码的生命周期,但我不能说服自己标准要求将其生命周期延长的临时性复制到临时复制的任何附加副本; 它的价值很小 - 它在我的程序中"有效",有趣的是临时占用了相同的堆栈位置,如果忽略了返回值,这表明f()代码是有效编译的,具有消除能力和-f-no-elide-constructors 选择不是禁用优化,而是为了添加悲观化:在调用函数之前留出额外的堆栈空间,然后添加额外的代码从中复制并破坏临时然后重新调整堆栈指针... .


Mat*_*son 6

返回值的副本由被调用者完成,并且必须在调用析构函数之前完成,否则您将无法返回本地构造的变量的值/内容.

这是标准中的相关部分:第12.4节,第11点(析构函数)

隐式调用析构函数

  • 对于具有自动存储持续时间(3.7.3)的构造对象,当创建对象的块退出时(6.7)

我试图找到一个地方,它说"回归发生在破坏之前",但它并没有像我想的那样清楚[除非我遗漏了什么].