提问人:Hanfei Sun 提问时间:7/4/2017 最后编辑:Baum mit AugenHanfei Sun 更新时间:7/4/2017 访问量:370
是矩形 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
(会员)使用赋值运算符复制到
tmp
B
Rectangle& operator = (const Rectangle &)
这个解释有意义吗?我想我可能理解错了,因为与 .Rectangle A(3, 4);
有人对此有想法吗?真的等同于吗?谢谢!Rectangle A(3,4)
Rectangle A = Rectangle(3, 4);
答:
Rectangle* C = new Rectangle(3,4);
,则代码中存在语法错误。operator =(Rectangle& rho)
是默认定义的返回引用。因此,不会如上所述创建对象。Rectangle B = Rectangle(3, 4);
tmp
评论
operator=
Rectangle C = new Rectangle(3,4);
operator=
真的在连续剧中做了三件事吗?
Rectangle B = Rectangle(3, 4);
不,这不是真的,但这并不是你的错:这令人困惑。
T obj2 = obj1
不是赋值,而是初始化。
所以你是对的,它将首先创建(在你的情况下,是临时的),但将使用复制构造函数构造,而不是默认构造然后分配给。obj1
obj2
对于 ,唯一的区别是临时变量的内存空间分配给堆而不是堆栈。
Rectangle C = new Rectangle(3, 4);
不,它不会编译,但会编译。这里已经有很多关于动态分配的解释。Rectangle* C
评论
Rectangle A(3,4)
Rectangle A = Rectangle(3, 4);
Rectangle A(3,4)
在这种情况下,实际发生的事情和理论上发生的事情之间存在显着差异。
第一种很简单:只需构造一个 Rectangle 并初始化为 和 。这一切都是使用您定义的构造函数在“一步”中完成的。简单明了 - 所以在可能的情况下,它通常是首选和推荐的。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);
始终简单地调用构造函数,贯穿 C++ 具有构造函数的所有历史。简单。无聊的。Rectangle(int, int)
现在是有趣的部分。
C++17
在最新版(在撰写本文时)标准中,无论 的移动或复制构造函数的性质是什么,都会立即折叠成 This happens 。此功能通常称为保证复制省略,但需要强调的是,这里没有复制,也没有移动。发生的情况是从 直接初始化的。Rectangle B = Rectangle(3,4);
Rectangle B(3,4);
Rectangle
B
(3,4)
C++之前的17
在 C++17 之前,有一个临时构造,编译器可以对其进行优化(我的意思是它肯定会,除非你告诉它不要这样做)。但是您的事件顺序不正确。需要注意的是,这里不会发生任何分配。我们没有分配给 .我们正在建设.表格代码:Rectangle
B
B
T var = expr;
是复制初始化,它不是复制分配。因此,我们做两件事:
- 我们使用构造函数构造一个临时的
Rectangle
Rectangle(int, int)
- 该临时绑定直接绑定到隐式生成的移动(或复制,C++ 11 之前)构造函数中的引用,然后调用该构造函数 - 从临时构造函数执行成员级移动(或复制)。(或者,更准确地说,重载解析会选择给定 prvalue 的最佳构造函数
Rectangle
Rectangle
) - 临时在语句处被销毁。
如果删除了移动构造函数(或者,在 C++ 11 之前,复制构造函数被标记),则尝试以这种方式构造的格式不正确。如果不理会特殊成员函数(如本例中所示),则 of 的两个声明肯定会编译为相同的代码。private
B
A
B
如果实际删除类型,初始化的形式可能看起来更熟悉:B
auto B = Rectangle(3,4);
这就是 Herb Sutter 喜欢的所谓 AAA(几乎总是自动)风格的声明。这与 完全相同,只是首先推导 as 的类型。Rectangle B = Rectangle(3, 4)
B
Rectangle
评论