【问题标题】:C++ copy-construct construct-and-assign questionC ++复制构造构造和分配问题
【发布时间】:2011-01-28 14:22:30
【问题描述】:

这是《C++ Gotchas》一书第 56 条的摘录:

看到一个简单的并不少见 Y 对象的初始化写入 三种不同方式中的任何一种,好像 它们是等价的。

Y a( 1066 ); 
Y b = Y(1066);
Y c = 1066;

事实上,所有这三个 初始化可能会导致 在同一个目标代码中 生成,但它们不等价。 a 的初始化称为 a 直接初始化,它确实 正是人们所期望的。这 初始化是通过 直接调用 Y::Y(int)。

b 和 c 的初始化是 更复杂。其实他们也 复杂的。这两个都是副本 初始化。在这种情况下 b的初始化,我们要求 创建一个匿名临时 Y 类型,用值初始化 1066. 然后我们使用这个匿名临时作为参数来复制 用于初始化类 Y 的构造函数 湾。最后,我们调用析构函数 匿名临时。

为了测试这一点,我做了一个带有数据成员的简单类(程序附在末尾),结果令人惊讶。似乎对于 c 的情况,对象是由复制构造函数构造的,而不是像书中建议的那样。

有人知道语言标准是否发生了变化,或者这仅仅是编译器的优化功能吗?我使用的是 Visual Studio 2008。

代码示例:

#include <iostream>

class Widget
{
    std::string name;
public:
    // Constructor
    Widget(std::string n) { name=n; std::cout << "Constructing Widget " << this->name << std::endl; }
    // Copy constructor
    Widget (const Widget& rhs) { std::cout << "Copy constructing Widget from " << rhs.name << std::endl; }
    // Assignment operator
    Widget& operator=(const Widget& rhs) { std::cout << "Assigning Widget from " << rhs.name << " to " << this->name << std::endl; return *this; }
};

int main(void)
{
    // construct
    Widget a("a");
    // copy construct
    Widget b(a);
    // construct and assign
    Widget c("c"); 
    c = a;
    // copy construct!
    Widget d = a;
    // construct!
    Widget e = "e";
    // construct and assign
    Widget f = Widget("f");

    return 0;
}

输出:

Constructing Widget a

Copy constructing Widget from a

Constructing Widget c
Assigning Widget from a to c

Copy constructing Widget from a

Constructing Widget e

Constructing Widget f
Copy constructing Widget from f

我对构造 d 和 e 的结果感到最惊讶。准确地说,我期望创建一个空对象,然后创建一个对象并将其分配给空对象。实际上,对象是由复制构造函数创建的。

【问题讨论】:

  • "在初始化 b 的情况下,我们请求创建一个类型为 Y 的匿名临时对象,初始化值为 1066"...在初始化 c 时也是如此,只是更难看到临时的。
  • 请注意,如果您的构造函数被声明为explicit...,则Y c = 1006 是不可能的,因为大多数时候任何一个参数构造函数都应该这样做。
  • Matthieu,是的,我同意你所说的,我在编程时总是这样做。如果我没记错的话,您的观点也包含在“更有效的 C++”一书中。

标签: c++ constructor copy-constructor assignment-operator


【解决方案1】:

从 C++17 开始,所有这三个 是等效的(除非 Y::Y(int)explicit,这将简单地禁止 c),因为通常称为 mandatory copy elision .

即使Y c = 1066; 也只会创建一个Y 对象,因为隐式转换为Y 的结果是用于初始化c 而不是创建临时对象的prvalue。

【讨论】:

    【解决方案2】:

    语法

    X a = b;
    

    a 和 b 是 X 类型的地方总是意味着复制构造。任何变体,例如:

    X a = X();
    

    被使用了,没有任务正在进行,而且从来没有。构造和分配将类似于:

    X a;
    a = X();
    

    【讨论】:

    • 感谢您对复制结构的评论。我并没有真正意识到这一点 - 我一直认为你必须做 X=a(b);确保调用复制构造函数。
    【解决方案3】:

    允许编译器将bc 优化为与a 相同。此外,编译器无论如何都可以完全消除复制构造和赋值运算符调用,因此您看到的任何内容对于不同的编译器甚至编译器设置都不一定相同。

    【讨论】:

    • 所以这意味着这本书现在可能不是 100% 正确?很有趣。
    • @Andy,没有这本书是 100% 正确的。但这不是 100% 完成的。如果你想 100% 完成,你需要参考标准。具体来说,它(或您引用的部分)没有解释如果复制是从上述临时完成的,则允许编译器优化副本。
    • 谢谢。我想你还是应该使用书中建议的格式,以防你使用的编译器没有实现优化。
    • 不,你不应该。使用更清晰的,不要担心那些小的优化。
    • @Johannes:“这本书说的是真话,不过是真话,但不是全部真相。” :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-29
    • 2016-12-19
    • 2013-09-15
    • 1970-01-01
    • 2020-05-14
    • 1970-01-01
    • 2013-10-13
    相关资源
    最近更新 更多