矩形 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);
真的连续做三件事是真的吗?
为 Rectangle 的临时变量(让我们使用
tmp
来表示它)分配内存空间,调用Rectangle::Rectangle(3, 4)
来初始化它。为变量
B
分配内存空间,使用默认构造函数初始化它(成员)使用赋值运算符
Rectangle& operator = (const Rectangle &)
将tmp
复制到B
这个解释有意义吗?我想我可能理解错了,因为与Rectangle A(3, 4);
相比,这个过程看起来非常笨拙和低效。
有人对此有想法吗?Rectangle A(3,4)
真的等同于Rectangle A = Rectangle(3, 4);
吗?谢谢!
Rectangle B = Rectangle(3, 4);
真的在序列中做三件事吗?
不,这不是真的,但这不是你的错:这令人困惑。
T obj2 = obj1
不是赋值,而是初始化。
所以你是对的,obj1
将首先创建(在您的情况下,是临时的),但obj2
将使用复制构造函数构造,而不是默认构造然后分配给。
对于
Rectangle C = new Rectangle(3, 4);
,唯一的区别是临时变量的内存空间被分配给堆而不是堆栈。
不,它不会编译,但Rectangle* C
会编译。这里已经有很多关于动态分配的解释。
在这种情况下,实际发生的事情与理论上发生的事情之间存在显着差异。
第一个很简单:Rectangle A(3, 4);
只是构造一个width
矩形,height
初始化为3
和4
。这一切都是使用您定义的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
。在这种情况下,如果如上所示删除赋值运算符,则代码将失败(无法编译)。
我们的一些误解似乎源于这样一个事实,即如果您不采取措施来阻止它这样做,编译器会自动为您创建"特殊成员函数"。您可以阻止它这样做的方法之一是上面显示的= delete;
语法,但这不是唯一的语法。例如,如果您的类包含引用类型的成员变量,则编译器不会为您创建赋值运算符。如果你从这样简单的事情开始:
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;
是复制初始化,不是复制赋值。因此,我们做两件事:
- 我们使用
Rectangle(int, int)
构造函数构造一个临时Rectangle
- 该临时直接绑定到隐式生成的移动(或复制,pre-C++11)构造函数中的引用,然后调用该构造函数 - 从临时构造函数执行成员级移动(或复制)。(或者,更准确地说,重载解析在给定类型为
Rectangle
的 prvalue 的情况下选择最佳Rectangle
构造函数) - 临时在声明中被销毁。
如果删除了移动构造函数(或者,在 C++11 之前,复制构造函数标记为private
),则尝试以这种方式构造B
格式不正确。如果保留特殊成员函数(如本例所示),则B
的两个A
声明肯定会编译为相同的代码。
如果实际删除类型,B
中的初始化形式可能看起来更熟悉:
auto B = Rectangle(3,4);
这就是赫伯·萨特(Herb Sutter)喜欢的所谓AAA(几乎总是自动)风格的声明。这与Rectangle B = Rectangle(3, 4)
完全相同,只是首先推导出B
类型为Rectangle
。
Rectangle* C = new Rectangle(3,4);
,您的代码中存在语法错误。operator =(Rectangle& rho)
是默认定义的返回引用。因此,Rectangle B = Rectangle(3, 4);
不会像上面描述的那样创建tmp
对象。
- 如何在QT中的自定义视频小工具t上绘制矩形
- 如何在Visual Basic中使用矩形函数OpenGL绘制矩形
- 矩形类中没有匹配函数
- 使用 stbi_write_png,如何将 0 和 1 的矩形字节数组转换为单色 png 文件?
- C++ SDL2:如何将矩形渲染到多个视口中
- 如何在 3D OpenGL 场景上绘制 SDL 2D 矩形?
- 如何在不使用静态矩形方法的情况下创建 IDOMPathGeometryPtr?
- SDL2 无法正确绘制矩形
- SFML白色矩形
- 解除分配矩形 2D 数组会根据尺寸产生错误
- 使用 SFML 在矩形的矢量中查找鼠标单击量
- 检查点是否位于 m 维矩形内
- 如何使用 opencv 绘制绿色矩形?
- 如何使用类获取矩形面积?
- SFML 在屏幕上移动矩形时滞后/小卡顿
- SDL/C++: 如何平滑但随机地移动矩形?
- 如何使用斐波那契和递归函数检查矩形是否是黄金矩形?
- 矩形 A = 矩形(3, 4);等效于矩形 A(3,4);
- 获得矩形向量矩形的最有效方法
- 高效的目标矩形源矩形剪裁