为什么在这种情况下不调用复制构造函数?

Why copy constructor is not called in this case?

提问人:BostonLogan 提问时间:11/19/2009 更新时间:3/30/2012 访问量:3765

问:

下面是小代码片段:

class A
{
public:
    A(int value) : value_(value)
    {
        cout <<"Regular constructor" <<endl;
    }

    A(const A& other)   : value_(other.value_)  
    {
        cout <<"Copy constructor" <<endl;
    }

private:
    int value_;
};
int main()
{
    A a = A(5);
}

我假设输出将是“常规构造函数”(对于 RHS),然后是 LHS 的“复制构造函数”。所以我避免了这种风格,并始终将类的变量声明为 .但令我惊讶的是,在上面的代码中,复制构造函数从未被调用(Visual C++ 2008)A a(5);

有谁知道这种行为是编译器优化的结果,还是C++的一些记录(和可移植)功能?谢谢。

C++ 复制构造函数

评论

0赞 jpinto3912 11/19/2009
它经过优化,避免了构造+复制。我发现这是一个很好的假设,即没有用户会从参数中构造,这与复制构造不同
0赞 Rob Kennedy 11/19/2009
Смотритетакже: stackoverflow.com/questions/1394229/...
1赞 Fred 11/19/2009
在 g++ 中,您可以使用选项 -fno-elide-constructors 禁用此优化

答:

15赞 GManNickG 11/19/2009 #1

来自另一条评论:“所以默认情况下我不应该依赖它(因为它可能取决于编译器)”

不,它几乎不依赖于编译器。任何有价值的编译器都不会浪费时间构建一个 A,然后复制它。

在标准中,它明确指出,它完全可以接受,等同于说。(§12.8.15,第211页)这样做显然是多余的,所以它去掉了内部的.T = x;T(x);T(T(x))T

若要获得所需的行为,需要强制编译器默认构造第一个 A:

A a;
// A is now a fully constructed object,
// so it can't call constructors again:
a = A(5);

评论

0赞 BostonLogan 11/19/2009
谢谢 GMan,反正我从不使用这种语法。只是要记住的事情。哦,刚刚在 msdn 上找到:C++ 标准允许省略复制构造函数(参见第 12.8 节。复制类对象,第 15 段)
0赞 GManNickG 11/19/2009
谢谢,我现在明白了。在答案中引用有点多,所以我只用数字来引用它。
1赞 Kirill V. Lyadvinsky 11/19/2009
这取决于编译器。标准允许不同的行为,请参阅我的答案。
4赞 Kirill V. Lyadvinsky 11/19/2009 #2

在这里,您有 from temporary 的复制初始化。根据 C++ 标准 12.2/2,允许在此处跳过调用复制构造函数的实现。aA(5)

评论

0赞 Michael Kristofik 11/20/2009
我认为这是不正确的。12.2.2 中的示例涉及在构造本地对象之前将临时传递给函数。
-1赞 Michael Kristofik 11/20/2009 #3
A a = A(5);

这条线相当于

A a(5);

尽管它的外观是函数式的,但第一行只是用参数 5 构造的。不涉及复制或临时。根据 C++ 标准,第 12.1.11 节:a

函数表示法类型转换 (5.2.3) 可用于创建其类型的新对象。[ 注: 语法看起来像构造函数的显式调用。——尾注 ]

评论

0赞 Ben Voigt 1/10/2012
不,它不是等价的。一种是复制初始化,另一种是直接初始化
7赞 Jon 3/30/2012 #4

我正在研究这个问题,以回答另一个被当作骗子而关闭的问题,所以为了不让工作白费,我回答了这个问题。

该形式的语句称为变量的复制初始化。C++11 标准 8.5/16 规定:A a = A(5)a

所选函数的初始值设定项表达式为 它的论点;如果函数是构造函数,则调用将初始化 目标类型的 CV 不合格版本的临时版本。这 temporary 是一个 prvalue。调用的结果(即临时的 对于构造函数情况),然后用于直接初始化,根据 根据上述规则,作为目标的对象 复制初始化。在某些情况下,允许实施 通过以下方式消除此直接初始化中固有的复制 将中间结果直接构造到对象中 初始 化;见12.2、12.8

这意味着编译器查找要处理的适当构造函数,创建一个临时构造函数,并将该临时构造函数复制到 中。但是在什么情况下可以消除副本呢?A(5)a

让我们看看 12.8/31 是怎么说的:

当满足某些条件时,允许实现省略 类对象的复制/移动构造,即使复制/移动 对象的构造函数和/或析构函数具有副作用。在 在这种情况下,实现将 省略了复制/移动操作,只是两种不同的引用方式 到同一个对象,并且该对象的破坏发生在 后来这两个物体会被摧毁 没有优化。这种复制/移动操作的省略, 在以下情况下允许使用“复制省略”(copy elision) (可以合并以消除多个副本):

[...]

  • 复制/移动未绑定到引用的临时类对象 (12.2) 时 对于具有相同 CV-Unqualified 类型的类对象,复制/移动操作可以是 通过将临时对象直接构造到省略的复制/移动的目标中来省略

考虑到所有这些,以下是表达式会发生什么:A a = A(5)

  1. 编译器看到带有复制初始化的声明
  2. 选择构造函数以初始化临时对象A(int)
  3. 由于临时对象绑定到引用,并且它与复制初始化表达式中的目标类型相同,因此允许编译器直接将对象构造为 ,省略临时Aa