繁体   English   中英

是矩形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类定义任何复制构造函数或赋值运算符。

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

  1. 为Rectangle的临时变量(让我们用tmp表示)分配内存空间,调用Rectangle::Rectangle(3, 4)初始化它。

  2. 为变量B分配内存空间,并使用默认构造函数对其进行初始化

  3. (以成员身份)使用赋值运算符Rectangle& operator = (const Rectangle &)tmp复制到B

这种解释有意义吗? 我认为我可能会理解错误,因为与Rectangle A(3, 4);相比,此过程看起来非常笨拙且效率低下Rectangle A(3, 4);

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

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

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

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

因此,您将首先创建obj1是正确的(在您的情况下是临时的),但是obj2将使用copy构造函数构造 ,而不是默认构造然后分配给。


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

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

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

第一个很简单: Rectangle A(3, 4); 只是构造一个Rectangle,其widthheight初始化为34 所有操作都使用Rectangle(int, int);在“一步”中完成Rectangle(int, int); 您定义的构造函数。 简单明了-因此尽可能地推荐并推荐它。

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

在实践中,会发生什么情况是,编译器检查,这能够创建临时对象,而且它是能够使用拷贝构造函数初始化B从暂时的。 在检查了这是可能的之后,几乎所有有能力的编译器(至少在启用优化时,甚至在没有启用优化时)都将生成与创建A基本上相同的代码。

但是,如果通过添加以下内容来删除复制构造函数:

Rectangle(Rectangle const &) = delete;

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

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

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

尽管看上去有点像创建B那条线,但是涉及的构造实际上更像您用来创建A (即使在理论上)仅创建一个对象。 它是从免费商店中分配的,并使用您的Rectangle(int, int);直接初始化Rectangle(int, int); 构造函数。

然后,该对象的地址用于初始化C (仅是一个指针)。 A的初始化一样,即使删除Rectangle的copy构造函数,它也将起作用,因为即使从理论上讲,也不会涉及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;
};

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

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

现在是有趣的部分。

C ++ 17

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

C ++ 17之前的版本

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

T var = expr;

是复制初始化 ,不是复制分配。 因此,我们做两件事:

  1. 我们使用Rectangle(int, int)构造函数构造一个临时Rectangle
  2. 该临时项直接绑定到隐式生成的move(或副本,C ++ 11之前的版本)构造函数中的引用,然后调用该构造函数-从临时项进行成员级移动(或副本)。 (或者,更准确地说,在给定Rectangle类型的prvalue的情况下,重载分辨率选择最佳的Rectangle构造函数)
  3. 该语句将临时文件销毁。

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


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

auto B = Rectangle(3,4);

这是Herb Sutter拥护的所谓AAA(几乎始终为自动)样式。 这与Rectangle B = Rectangle(3, 4)功能完全相同,只是第一步是首先将B的类型推导为Rectangle

  1. Rectangle* C = new Rectangle(3,4); ,您的代码中有语法错误。
  2. operator =(Rectangle& rho)是默认定义的返回参考。 因此, Rectangle B = Rectangle(3, 4); 不会如上所述创建tmp对象。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM