是矩形 A = 矩形(3, 4);相当于矩形 A(3,4);?

Is Rectangle A = Rectangle(3, 4); equivalent to Rectangle A(3,4);?

下面是我的代码:

#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 class.

定义任何复制构造函数或赋值运算符

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

  1. 为Rectangle的一个临时变量(我们用tmp表示)分配内存space,调用Rectangle::Rectangle(3, 4)初始化

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

  3. (memberwise) 使用赋值运算符 Rectangle& operator = (const Rectangle &)

  4. tmp 复制到 B

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

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

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

Is it true that Rectangle B = Rectangle(3, 4); actually does three things in serial?

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

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

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


For Rectangle C = new Rectangle(3, 4);, the only difference is that the memory space of the temporary variable is allocated to the heap instead of stack.

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

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

第一个很简单:Rectangle A(3, 4); 只是用 widthheight 构造一个 Rectangle 初始化为 34。这一切都是在 "one step" 中使用您定义的 Rectangle(int, int); 构造函数完成的。简单明了——因此在可能的情况下,它通常是首选和推荐的。

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

实际上,编译器检查它是否能够创建临时对象,并且它是否能够使用复制构造函数来初始化B 从那个临时的。在检查了这是可能的之后,几乎所有有能力的编译器(至少在启用优化时,甚至在未启用优化时)都会生成与创建 A.

时所做的基本相同的代码。

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

Rectangle(Rectangle const &) = delete;

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

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

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

尽管 看起来 有点像上面创建 B 的行,但所涉及的构造实际上更像您用来创建 [=24= 的前一个构造].只创建了一个对象(甚至理论上)。它是从免费商店分配的,并使用您的 Rectangle(int, int); 构造函数直接初始化。

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

None 其中涉及赋值运算符。如果要删除赋值运算符:

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

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

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

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

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

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

struct Rectangle { 
    int width, height;
};

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

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-initialization,不是copy-assignment。因此,我们做了两件事:

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

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


如果您实际删除类型,B 中的初始化形式可能看起来更熟悉:

auto B = Rectangle(3,4);

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