以下是我的代码:
#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);
实际上连续做了三件事是真的吗?
为Rectangle分配临时变量(让我们使用tmp
表示)的内存空间,调用Rectangle::Rectangle(3, 4)
初始化它。
为变量B
分配内存空间,使用默认构造函数初始化
(成员)使用赋值运算符tmp
将<{1}}复制到B
这种解释有意义吗?我想我可能会错误地理解,因为与Rectangle& operator = (const Rectangle &)
相比,这个过程看起来非常笨拙和低效。
有没有人有这方面的想法? Rectangle A(3, 4);
是否等同于Rectangle A(3,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);
只构建一个width
和height
初始化为3
和4
的矩形。这一切都在&#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;
这仍然只使用构造函数来创建和初始化A
和B
,但随后使用赋值运算符将A
的值赋给B
。在这种情况下,如果您删除了如上所示的赋值运算符,则代码将失败(无法编译)。
我们的一些误解似乎源于编译器自动创建&#34;特殊成员函数&#34;如果你没有采取措施阻止它这样做,请为你服务。阻止它这样做的一种方法是上面显示的= delete;
语法,但这不是唯一的方法。例如,如果您的类包含引用类型的成员变量,则编译器不会为您创建赋值运算符。如果你从这样简单的事情开始:
struct Rectangle {
int width, height;
};
...编译器将完全自动生成默认构造函数,复制构造函数,移动构造函数,复制赋值和移动赋值运算符。
答案 2 :(得分:2)
Rectangle A(3, 4);
总是简单地调用Rectangle(int, int)
构造函数。简单。无聊。
现在为有趣的部分。
在标准的最新版本(撰写本文时)中Rectangle B = Rectangle(3,4);
立即崩溃为Rectangle B(3,4);
无论Rectangle
移动的性质如何,都会发生这种情况或复制构造函数是。这个功能通常被称为保证副本省略,尽管强调这里没有副本也没有移动很重要。会发生什么B
是从(3,4)
直接初始化的。
在C ++ 17之前,有一个临时的Rectangle
构造成编译器可以优化掉(并且可能,我的意思是肯定会,除非你告诉它不要)。但是您的活动顺序不正确。重要的是要注意此处没有分配。我们没有分配给B
。我们正在构建B
。表格代码:
T var = expr;
是copy- 初始化,它不是复制分配。因此,我们做了两件事:
Rectangle
构造函数Rectangle(int, int)
Rectangle
构造函数,给出Rectangle
类型的prvalue 如果移动构造函数被删除(或者,在C ++ 11之前,复制构造函数被标记为private
),那么尝试以这种方式构造B
是不正确的。如果特殊成员函数保持不变(如本例所示),A
B
的两个声明肯定会编译为相同的代码。
如果您实际删除了类型,B
中的初始化形式可能会更加熟悉:
auto B = Rectangle(3,4);
这就是Herb Sutter所谓的AAA(几乎总是自动)式的声明。这与Rectangle B = Rectangle(3, 4)
完全相同,只是首先将B
的类型推断为Rectangle
。
答案 3 :(得分:-3)
Rectangle* C = new Rectangle(3,4);
,您的代码中存在语法错误。operator =(Rectangle& rho)
默认定义为返回引用。因此,Rectangle B = Rectangle(3, 4);
不会像上面描述的那样创建tmp
对象。