【问题标题】:Is Rectangle A = Rectangle(3, 4); equivalent to Rectangle A(3,4);?是矩形 A = 矩形 (3, 4);相当于矩形 A(3,4);?
【发布时间】:2017-12-07 07:27:27
【问题描述】:

下面是我的代码:

#include <iostream>
using namespace std;

class Rectangle {
  int width, height;
 public:
  Rectangle(int, int);
  int area() { return (width * height); }
};

Rectangle::Rectangle(int a, int b) {

  width = a;
  height = b;
}

int main() {
  Rectangle A(3, 4);
  Rectangle B = Rectange(3,4);
  return 0;
}

我没有为Rectangle 类定义任何复制构造函数或赋值运算符。

Rectangle B = Rectangle(3, 4); 真的是连续做了三件事吗?

  1. 为一个Rectangle的临时变量(我们用tmp来表示)分配内存空间,调用Rectangle::Rectangle(3, 4)对其进行初始化。

  2. 为变量B分配内存空间,用默认构造函数初始化

  3. (按成员)使用赋值运算符 Rectangle&amp; operator = (const Rectangle &amp;)tmp 复制到 B

这种解释有意义吗?我想我可能理解错了,因为与Rectangle A(3, 4);相比,这个过程看起来非常笨拙和低效。

有人对此有想法吗? Rectangle A(3,4) 是否等同于 Rectangle A = Rectangle(3, 4);?谢谢!

【问题讨论】:

  • @JerryCoffin 感谢您的指正!我混淆了 Java 语法...

标签: c++ class initialization assignment-operator


【解决方案1】:
  1. Rectangle* C = new Rectangle(3,4);,你的代码有语法错误。
  2. operator =(Rectangle&amp; rho) 是默认定义的返回引用。因此,Rectangle B = Rectangle(3, 4); 不会像您上面描述的那样创建 tmp 对象。

【讨论】:

  • 1.这不是语法错误。 2.operator=与此无关
  • 帖子说Rectangle C = new Rectangle(3,4);我知道这是错的。
  • 我写了一个类,用operator=打印消息,结果显示你的评论是对的。非常感谢。
【解决方案2】:

Rectangle B = Rectangle(3, 4); 真的是连续做了三件事吗?

不,这不是真的,但这并不是你的错:这令人困惑。

T obj2 = obj1 不是赋值,而是初始化

所以你是对的,obj1 将首先被创建(在你的情况下,一个临时的),但 obj2 将使用 复制构造函数 构造,而不是默认构造然后分配 -到。


对于Rectangle C = new Rectangle(3, 4);,唯一不同的是临时变量的内存空间分配给堆而不是栈。

不,它不会编译,但Rectangle* C 会。这里已经有很多关于动态分配的解释了。

【讨论】:

  • 谢谢,那么Rectangle A(3,4) 是否等同于Rectangle A = Rectangle(3, 4);?如果不是,首选哪一个?好像Rectangle A(3,4)比较常用吧?
  • @HanfeiSun:不完全等效(直到 C++17),但在这种情况下,功能上是相同的。是的。
【解决方案3】:

在这种情况下,实际发生的事情与理论上发生的事情之间存在显着差异。

第一个很简单:Rectangle A(3, 4); 只是构造了一个 Rectangle,其中 widthheight 初始化为 34。这一切都是使用您定义的Rectangle(int, int); 构造函数“一步”完成的。简单明了 - 因此,如果可能,它通常是首选和推荐的。

那么让我们考虑一下:Rectangle B = Rectangle(3,4);。理论上,这会构造一个临时对象,然后从该临时对象复制构造 B

实际上,编译器会检查它是否能够创建临时对象,以及它是否能够使用复制构造函数从该临时对象初始化B .在检查了这是否可行之后,几乎任何有能力的编译器(至少在启用优化时,甚至经常在未启用优化时)都会生成与创建 A 时所做的基本相同的代码。

但是,如果您删除复制构造函数,则添加:

Rectangle(Rectangle const &) = delete;

...然后编译器会发现它无法从临时文件中复制构造B,并拒绝编译代码。即使最终生成的代码从未真正使用复制构造函数,它也必须可供它工作。

最后,让我们看一下您的第三个示例(语法已更正):

Rectangle *C = new Rectangle(3, 4);

尽管看起来有点像上面创建B 的那一行,但所涉及的构造实际上更像你用来创建A 的前一个构造。只有 one 对象(甚至理论上)被创建。它是从免费存储中分配的,并使用您的 Rectangle(int, int); 构造函数直接初始化。

然后那个对象的地址被用来初始化C(它只是一个指针)。就像A 的初始化一样,即使Rectangle 的复制构造函数被删除,这也会起作用,因为即使在理论上也不会涉及到Rectangle 的复制。

这些都不涉及赋值运算符。如果要删除赋值运算符:

Rectangle &operator=(Rectangle const &) = delete;

...所有这些代码仍然可以,因为(尽管使用了=)代码中的任何地方都没有赋值。

要使用作业(如果您真的坚持这样做),您可以(例如)执行以下操作:

Rectangle A(3, 4);
Rectangle B = Rectangle(5, 6);
B = A;

这仍然只使用构造函数来创建和初始化AB,但随后使用赋值运算符将A 的值赋给B。在这种情况下,如果您删除了如上所示的赋值运算符,代码将失败(无法编译)。

我们的一些误解似乎源于这样一个事实,即编译器会自动为您创建“特殊成员函数”,如果您不采取措施阻止它这样做。阻止它这样做的方法之一是上面显示的= delete; 语法,但这不是唯一的。例如,如果您的类包含引用类型的成员变量,编译器不会为您创建赋值运算符。如果你从这样简单的事情开始:

struct Rectangle { 
    int width, height;
};

...编译器将自动生成默认构造函数、复制构造函数、移动构造函数、复制赋值和移动赋值运算符。

【讨论】:

    【解决方案4】:

    Rectangle A(3, 4); 总是简单地调用 Rectangle(int, int) 构造函数,在 C++ 的所有历史中都有构造函数。简单的。无聊的。

    现在是有趣的部分。

    C++17

    在标准的最新版本中(截至撰写本文时),Rectangle B = Rectangle(3,4); 立即折叠为Rectangle B(3,4);,无论Rectangle 的移动或复制构造函数的性质如何,都会发生这种情况。这个特性通常被称为保证复制省略,尽管重要的是要强调这里没有复制也没有移动。发生的情况是 B 直接从 (3,4) 初始化。

    C++17 之前

    在 C++17 之前,有一个临时的 Rectangle 构造,编译器可能会优化掉它(可能,我的意思是它肯定会优化,除非你告诉它不要这样做)。但是您的事件顺序不正确。重要的是要注意这里没有分配发生。我们没有分配给B。我们正在构建B。表格代码:

    T var = expr;
    

    是copy-初始化,不是copy-assignment。因此,我们做了两件事:

    1. 我们使用Rectangle(int, int) 构造函数构造一个临时的Rectangle
    2. 该临时文件直接绑定到隐式生成的移动(或复制,C++11 之前的版本)构造函数中的引用,然后调用该构造函数 - 从临时文件中执行逐个成员移动(或复制)。 (或者,更准确地说,重载决议选择最佳 Rectangle 构造函数给定类型的纯右值 Rectangle
    3. 临时在声明中被销毁。

    如果删除了移动构造函数(或者,在 C++11 之前,复制构造函数被标记为 private),那么尝试以这种方式构造 B 是不正确的。如果不考虑特殊成员函数(如本例中所示),B 的两个声明 A 肯定会编译为相同的代码。


    B 中的初始化形式如果你真的放弃类型可能看起来更熟悉:

    auto B = Rectangle(3,4);
    

    这就是 Herb Sutter 喜欢的所谓的 AAA(几乎总是自动)风格的声明。这与Rectangle B = Rectangle(3, 4) 完全相同,只是首先将B 的类型推导出为Rectangle

    【讨论】:

    • "Rectangle A(3, 4); 总是简单地调用 Rectangle(int, int) 构造函数,贯穿 C++ 构造函数的所有历史。简单。无聊。" 嗯,不,它的作用不止于此。
    • “在最新(撰写本文时)版本的标准中” C++17 还不存在。 C++14 是该标准的最新版本(在撰写本文时)。
    • “编译器可能会优化掉(可能,我的意思是它肯定会优化,除非你告诉它不要这样做)” 是的,但是仍然有“可见的效果”例如要求复制/移动 ctor 是可访问的。
    • “我们使用 Rectangle(int, int) 构造函数构造一个临时 Rectangle” 技术上准确(因为该 ctor 将被调用)但有点误导:我们从不直接调用一个构造函数,但这个措辞让我们听起来像我们做的那样
    • “临时在语句中被销毁。”在*的末尾
    猜你喜欢
    • 2019-04-07
    • 2013-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多