【问题标题】:Why copy constructor is not called in this case?为什么在这种情况下不调用复制构造函数?
【发布时间】:2009-11-18 18:40:05
【问题描述】:

这里是小代码sn-p:

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++ 的某些记录(和可移植)特性的结果?谢谢。

【问题讨论】:

  • 它已经过优化,避免了构造+复制。我发现这是一个很好的假设,即没有用户会根据参数进行构造,这与它所做的复制构造不同
  • 在 g++ 中,您可以使用选项 -fno-elide-constructors 禁用此优化

标签: c++ constructor copy-constructor


【解决方案1】:

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

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

在标准中明确表示T = x; 等同于T(x); 是完全可以接受的。 (§12.8.15, pg. 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);

【讨论】:

  • 感谢 GMan,反正我从不使用这种语法。只是要记住的东西。哦,刚刚在 msdn 上找到:C++ 标准允许省略复制构造函数(参见第 12.8 节。复制类对象,第 15 段)
  • 谢谢,我现在看到了。在回答中引用的内容有点多,所以我将按数字引用它。
  • 这取决于编译器。标准允许不同的行为,请参阅我的答案。
【解决方案2】:

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

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,省略临时

【讨论】:

    【解决方案3】:

    这里有来自临时A(5)a复制初始化。根据 C++ 标准 12.2/2,实现允许在此处跳过调用复制构造函数。

    【讨论】:

    • 我不认为这是正确的。 12.2.2 中的示例涉及在构造本地对象之前将临时传递给函数。
    【解决方案4】:
    A a = A(5);
    

    这一行相当于

    A a(5);
    

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

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

    【讨论】:

    • 不,不等价。一种是copy-initialization,一种是direct-initialization
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-06
    • 2016-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多