C++复制构造函数、临时对象和复制语义

16
对于这个程序
#include <iostream>
using std::cout;

struct C 
{
    C() { cout << "Default C called!\n"; }
    C(const C &rhs) { cout << "CC called!\n"; }
};

const C f()
{
    cout << "Entered f()!\n";
    return C();
}

int main()
{
    C a = f();
    C b = a;

    return 0;
}

我收到的输出是:
Entered f()!
Default C called!
CC called!

由于f()返回的是值,因此它应该返回一个临时对象。由于T a = x;等同于T a(x);,那么在构造a时,会调用复制构造函数,并将临时对象作为参数传入。

3个回答

12
由于f()返回的是值,因此它应该返回一个临时对象。由于T a = x;等同于T a(x);,那么在构造a时会调用复制构造函数,并将临时对象作为参数传递进去吗?
请查看返回值优化(Return Value Optimization)。这个功能默认开启。如果你使用的是Windows操作系统并且使用MSVC 2005+编译器,你可以使用/Od关闭这个功能并获得期望的结果(或者在GCC上使用-fno-elide-constructors)。此外,对于MSVC,请参考这篇文章12.8 复制类对象 当满足特定条件时,实现允许省略类对象的复制构造函数,即使该对象的复制构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制操作的源和目标视为仅是两种不同的引用方式,而省略该优化的两个对象的销毁时间取决于它们在没有优化的情况下被销毁的时间。这种复制操作的省略在以下情况下允许(可以组合以消除多个复制):
- 在具有类返回类型的函数中的return语句中,当表达式是与函数返回类型具有相同cv-unqualified类型的非易失性自动对象的名称时,可以通过将自动对象直接构造到函数的返回值中来省略复制操作 - 在throw-expression中,当操作数是非易失性自动对象的名称时,可以通过将自动对象直接构造到异常对象中来省略从操作数到异常对象(15.1)的复制操作 - 当一个未绑定到引用(12.2)的临时类对象将被复制到具有相同cv-unqualified类型的类对象时,可以通过将临时对象直接构造到省略复制的目标中来省略复制操作 - 当异常处理程序(Clause 15)的exception-declaration声明与异常对象(15.1)具有相同类型(除了cv-qualification),如果程序的含义除了执行由exception-declaration声明的对象的构造函数和析构函数之外不会改变,则可以通过将exception-declaration视为异常对象的别名来省略复制操作。 注意:强调是我的。

我使用GCC,并且通过-fno-elide-constructors选项,它可以准确地显示发生了什么!有时这些优化会让学习者感到困惑 :) 然而,我同意默认情况下应该开启它们,因为对于一个不知情的人来说,默认情况下构建将被优化。 - legends2k
@legends2k:RVO(返回值优化)太有用了,不能随意让用户决定。此外,这是极少数标准允许优化的情况之一。这就是为什么它被保留的原因。但是,请注意,这通常不适用于其他优化。 - dirkgently

4

这是一个关于返回值优化(RVO)的示例,您的编译器支持该功能。

当您通过值返回时,可能不会调用复制构造函数。

在GCC上使用-fno-elide-constructors选项来关闭该功能。


2
我相信这被称为返回值优化
我认为当f()返回C对象时,该对象被分配在调用方法的堆栈空间中,因此不需要复制来初始化C a。这就是你的默认C被调用
C b = a

这会导致一个复制构造函数,因此你的CC called被调用了。
顺便说一下,维基上的例子看起来与你的代码非常相似。

+1 给维基百科链接。哎呀!连命名都看起来相似,但我发誓我没有在阅读那篇文章之后发布,我正在阅读《C++ 编程思想》 :) - legends2k

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