13

这是小代码片段:

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。所以我避免了这种风格,总是将类的变量声明为A a(5);. 但令我惊讶的是,上面的代码从未调用过复制构造函数(Visual C++ 2008)

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

4

4 回答 4

15

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

不,它不依赖于编译器,实际上无论如何。任何有价值的编译器都不会浪费时间构建 A,然后将其复制过来。

在标准中,它明确表示完全可以接受 forT = x;等同于 say T(x);。(第 12.8.15 节,第 211 页)这样做T(T(x))显然是多余的,因此它删除了内部T.

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

A a;
// A is now a fully constructed object,
// so it can't call constructors again:
a = A(5);
于 2009-11-18T18:53:02.790 回答
7

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

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

以初始化表达式作为参数调用所选函数;如果函数是构造函数,则调用初始化目标类型的 cv 非限定版本的临时版本。临时是prvalue。然后根据上述规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除这种直接初始化中固有的复制;见 12.2、12.8

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

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

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标简单地视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本应被删除的较晚时间。没有优化就被破坏了。这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以结合起来消除多个副本):

[...]

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

考虑到这一切,这就是表达式发生的事情A a = A(5)

  1. 编译器看到一个带有复制初始化的声明
  2. 构造A(int)函数被选中初始化一个临时对象
  3. 因为临时对象没有绑定到引用,并且它确实A与复制初始化表达式中的目标类型具有相同的类型,所以允许编译器直接将对象构造成a,省略临时
于 2012-03-30T15:06:03.100 回答
4

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

于 2009-11-18T19:54:19.203 回答
-1
A a = A(5);

这条线相当于

A a(5);

尽管它具有函数式外观,但第一行仅a使用参数 5 进行构造。不涉及复制或临时性。从 C++ 标准,第 12.1.11 节:

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

于 2009-11-19T17:46:09.027 回答