是矩形 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
class.
定义任何复制构造函数或赋值运算符
Rectangle B = Rectangle(3, 4);
真的是连续做三件事吗?
为Rectangle的一个临时变量(我们用tmp
表示)分配内存space,调用Rectangle::Rectangle(3, 4)
初始化
为变量B
分配内存space,用默认构造函数初始化
(memberwise) 使用赋值运算符 Rectangle& operator = (const Rectangle &)
将 tmp
复制到 B
这个解释有道理吗?我想我可能理解错了,因为与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
对象。
Is it true that Rectangle B = Rectangle(3, 4);
actually does three things in serial?
不,那不是真的,但这并不是你的错:这令人困惑。
T obj2 = obj1
不是赋值,而是初始化.
所以你是对的 obj1
将首先创建(在你的情况下,一个临时的),但是 obj2
将使用 复制构造函数, 不是默认构造然后分配给.
For Rectangle C = new Rectangle(3, 4);
, the only difference is that the memory space of the temporary variable is allocated to the heap instead of stack.
不,它不会编译,但 Rectangle* C
会。这里已经有很多关于动态分配的解释。
在这种情况下,实际 发生的情况与理论上发生的情况之间存在显着差异。
第一个很简单:Rectangle A(3, 4);
只是用 width
和 height
构造一个 Rectangle 初始化为 3
和 4
。这一切都是在 "one step" 中使用您定义的 Rectangle(int, int);
构造函数完成的。简单明了——因此在可能的情况下,它通常是首选和推荐的。
那我们考虑一下:Rectangle B = Rectangle(3,4);
。理论上,这会构造一个临时对象,然后从该临时对象复制构造 B
。
实际上,编译器检查它是否能够创建临时对象,并且它是否能够使用复制构造函数来初始化B
从那个临时的。在检查了这是可能的之后,几乎所有有能力的编译器(至少在启用优化时,甚至在未启用优化时)都会生成与创建 A
.
时所做的基本相同的代码。
但是,如果删除复制构造函数,则添加:
Rectangle(Rectangle const &) = delete;
...然后编译器会发现它无法从临时文件中复制构造 B
,并且会拒绝编译代码。即使最终生成的代码实际上从未使用复制构造函数,它也必须可用才能工作。
最后,让我们看一下您的第三个示例(已更正语法):
Rectangle *C = new Rectangle(3, 4);
尽管 看起来 有点像上面创建 B
的行,但所涉及的构造实际上更像您用来创建 [=24= 的前一个构造].只创建了一个对象(甚至理论上)。它是从免费商店分配的,并使用您的 Rectangle(int, int);
构造函数直接初始化。
然后那个对象的地址用来初始化C
(这只是一个指针)。就像 A
的初始化一样,即使 Rectangle
的复制构造函数被删除,这也会起作用,因为即使在理论上也不涉及 Rectangle
的复制。
None 其中涉及赋值运算符。如果要删除赋值运算符:
Rectangle &operator=(Rectangle const &) = delete;
...所有这些代码仍然可以,因为(尽管使用了 =
)代码中的任何地方都没有赋值。
要使用赋值(如果您真的坚持这样做),您可以(例如)执行以下操作:
Rectangle A(3, 4);
Rectangle B = Rectangle(5, 6);
B = A;
这仍然只使用构造函数来创建和初始化A
和B
,但随后使用赋值运算符将A
的值赋给B
。在这种情况下,如果您如上所示删除赋值运算符,代码将失败(无法编译)。
我们的一些误解似乎源于这样一个事实,即如果您不采取措施阻止它这样做,编译器会自动为您创建 "special member functions"。您可以阻止它这样做的方法之一是上面显示的 = delete;
语法,但这不是唯一的方法。例如,如果您的 class 包含引用类型的成员变量,编译器将不会为您创建赋值运算符。如果你从像这样简单的事情开始:
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;
是copy-initialization,不是copy-assignment。因此,我们做了两件事:
- 我们使用
Rectangle(int, int)
构造函数 构造一个临时的 Rectangle
- 该临时对象直接绑定到隐式生成的移动(或复制,C++11 之前的版本)构造函数中的引用,然后调用该构造函数 - 从临时对象执行成员智能移动(或复制)。 (或者,更准确地说,重载决策选择最好的
Rectangle
给定 Rectangle
类型的纯右值的构造函数)
- 临时文件在语句处销毁。
如果移动构造函数被删除(或者,在 C++11 之前,复制构造函数被标记为 private
),那么试图以这种方式构造 B
是错误的。如果保留特殊成员函数(如本例所示),B
和 A
的两个声明肯定会编译为相同的代码。
如果您实际删除类型,B
中的初始化形式可能看起来更熟悉:
auto B = Rectangle(3,4);
这就是 Herb Sutter 所钟爱的所谓 AAA(几乎总是自动)声明风格。这与 Rectangle B = Rectangle(3, 4)
的作用完全相同,只是首先将 B
的类型推断为 Rectangle
的类型。
下面是我的代码:
#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
class.
Rectangle B = Rectangle(3, 4);
真的是连续做三件事吗?
为Rectangle的一个临时变量(我们用
tmp
表示)分配内存space,调用Rectangle::Rectangle(3, 4)
初始化为变量
B
分配内存space,用默认构造函数初始化(memberwise) 使用赋值运算符
Rectangle& operator = (const Rectangle &)
将
tmp
复制到 B
这个解释有道理吗?我想我可能理解错了,因为与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
对象。
Is it true that
Rectangle B = Rectangle(3, 4);
actually does three things in serial?
不,那不是真的,但这并不是你的错:这令人困惑。
T obj2 = obj1
不是赋值,而是初始化.
所以你是对的 obj1
将首先创建(在你的情况下,一个临时的),但是 obj2
将使用 复制构造函数, 不是默认构造然后分配给.
For
Rectangle C = new Rectangle(3, 4);
, the only difference is that the memory space of the temporary variable is allocated to the heap instead of stack.
不,它不会编译,但 Rectangle* C
会。这里已经有很多关于动态分配的解释。
在这种情况下,实际 发生的情况与理论上发生的情况之间存在显着差异。
第一个很简单:Rectangle A(3, 4);
只是用 width
和 height
构造一个 Rectangle 初始化为 3
和 4
。这一切都是在 "one step" 中使用您定义的 Rectangle(int, int);
构造函数完成的。简单明了——因此在可能的情况下,它通常是首选和推荐的。
那我们考虑一下:Rectangle B = Rectangle(3,4);
。理论上,这会构造一个临时对象,然后从该临时对象复制构造 B
。
实际上,编译器检查它是否能够创建临时对象,并且它是否能够使用复制构造函数来初始化B
从那个临时的。在检查了这是可能的之后,几乎所有有能力的编译器(至少在启用优化时,甚至在未启用优化时)都会生成与创建 A
.
但是,如果删除复制构造函数,则添加:
Rectangle(Rectangle const &) = delete;
...然后编译器会发现它无法从临时文件中复制构造 B
,并且会拒绝编译代码。即使最终生成的代码实际上从未使用复制构造函数,它也必须可用才能工作。
最后,让我们看一下您的第三个示例(已更正语法):
Rectangle *C = new Rectangle(3, 4);
尽管 看起来 有点像上面创建 B
的行,但所涉及的构造实际上更像您用来创建 [=24= 的前一个构造].只创建了一个对象(甚至理论上)。它是从免费商店分配的,并使用您的 Rectangle(int, int);
构造函数直接初始化。
然后那个对象的地址用来初始化C
(这只是一个指针)。就像 A
的初始化一样,即使 Rectangle
的复制构造函数被删除,这也会起作用,因为即使在理论上也不涉及 Rectangle
的复制。
None 其中涉及赋值运算符。如果要删除赋值运算符:
Rectangle &operator=(Rectangle const &) = delete;
...所有这些代码仍然可以,因为(尽管使用了 =
)代码中的任何地方都没有赋值。
要使用赋值(如果您真的坚持这样做),您可以(例如)执行以下操作:
Rectangle A(3, 4);
Rectangle B = Rectangle(5, 6);
B = A;
这仍然只使用构造函数来创建和初始化A
和B
,但随后使用赋值运算符将A
的值赋给B
。在这种情况下,如果您如上所示删除赋值运算符,代码将失败(无法编译)。
我们的一些误解似乎源于这样一个事实,即如果您不采取措施阻止它这样做,编译器会自动为您创建 "special member functions"。您可以阻止它这样做的方法之一是上面显示的 = delete;
语法,但这不是唯一的方法。例如,如果您的 class 包含引用类型的成员变量,编译器将不会为您创建赋值运算符。如果你从像这样简单的事情开始:
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;
是copy-initialization,不是copy-assignment。因此,我们做了两件事:
- 我们使用
Rectangle(int, int)
构造函数 构造一个临时的 - 该临时对象直接绑定到隐式生成的移动(或复制,C++11 之前的版本)构造函数中的引用,然后调用该构造函数 - 从临时对象执行成员智能移动(或复制)。 (或者,更准确地说,重载决策选择最好的
Rectangle
给定Rectangle
类型的纯右值的构造函数) - 临时文件在语句处销毁。
Rectangle
如果移动构造函数被删除(或者,在 C++11 之前,复制构造函数被标记为 private
),那么试图以这种方式构造 B
是错误的。如果保留特殊成员函数(如本例所示),B
和 A
的两个声明肯定会编译为相同的代码。
如果您实际删除类型,B
中的初始化形式可能看起来更熟悉:
auto B = Rectangle(3,4);
这就是 Herb Sutter 所钟爱的所谓 AAA(几乎总是自动)声明风格。这与 Rectangle B = Rectangle(3, 4)
的作用完全相同,只是首先将 B
的类型推断为 Rectangle
的类型。