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

时间:2017-07-04 00:59:01

标签: c++ class initialization assignment-operator

以下是我的代码:

#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. (成员)使用赋值运算符tmp将<{1}}复制到B

  4. 这种解释有意义吗?我想我可能会错误地理解,因为与Rectangle& operator = (const Rectangle &)相比,这个过程看起来非常笨拙和低效。

    有没有人有这方面的想法? Rectangle A(3, 4);是否等同于Rectangle A(3,4),这是真的吗?谢谢!

4 个答案:

答案 0 :(得分:3)

  

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

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

T obj2 = obj1不是作业,而是初始化

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

  

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

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

答案 1 :(得分:3)

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

第一个很简单:Rectangle A(3, 4);只构建一个widthheight初始化为34的矩形。这一切都在&#34;一步完成&#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的前一个构造。仅(理论上)创建了一个对象。它是从免费商店分配的,并使用您的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。在这种情况下,如果您删除了如上所示的赋值运算符,则代码将失败(无法编译)。

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

struct Rectangle { 
    int width, height;
};

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

答案 2 :(得分:2)

在具有构造函数的C ++的所有历史记录中,

Rectangle A(3, 4);总是简单地调用Rectangle(int, int)构造函数。简单。无聊。

现在为有趣的部分。

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- 初始化,它不是复制分配。因此,我们做了两件事:

  1. 我们使用Rectangle构造函数
  2. 构建临时Rectangle(int, int)
  3. 该临时绑定直接绑定到隐式生成的移动(或复制,前C ++ 11)构造函数中的引用,然后调用该构造函数 - 从临时执行成员移动(或复制)。 (或者,更确切地说,重载决策选择最佳Rectangle构造函数,给出Rectangle类型的prvalue
  4. 临时在声明中被销毁。
  5. 如果移动构造函数被删除(或者,在C ++ 11之前,复制构造函数被标记为private),那么尝试以这种方式构造B是不正确的。如果特殊成员函数保持不变(如本例所示),A B的两个声明肯定会编译为相同的代码。

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

    auto B = Rectangle(3,4);
    

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

答案 3 :(得分:-3)

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