Gus*_*ans 6 c++ struct unions assignment-operator
当我尝试在Visual Studio 2015中编译以下最小示例时,我遇到了误导性错误消息的问题:
class Vector
{
float x;
float y;
public:
Vector(float x, float y) : x(x), y(y) {}
Vector& operator = (const Vector& v) { x = v.x; y = v.y; return *this; }
//Vector(Vector&&) = default;
};
class Rect
{
public:
union {
struct {
Vector p1, p2;
};
struct {
float p1x, p1y, p2x, p2y;
};
};
Rect() : p1(0,0), p2(0,0) {}
Rect(Vector& p1, Vector& p2) : p1(p1), p2(p2) {}
/*Rect(const Rect&) = default;
Rect& operator=(const Rect&) = default;
Rect& operator=(Rect&&) = default;
Rect(Rect&&) = default;*/
};
int main()
{
Rect test = Rect();
test = Rect();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我收到以下错误消息:
1> ... main.cpp(56):错误C2280:'Rect&Rect :: operator =(const Rect&)':尝试引用已删除的函数
1> ... main.cpp(50):注意:编译器在这里生成了'Rect :: operator ='
编译器试图告诉我,类Rect的复制构造函数是一个已删除的函数.所以我尝试添加各种附加(复制)构造函数和赋值运算符,如下所示,但没有成功:
Rect(const Rect&) = default;
Rect& operator=(const Rect&) = default;
Rect& operator=(Rect&&) = default;
Rect(Rect&&) = default;
Run Code Online (Sandbox Code Playgroud)
我认识到错误实际上不是在Rect类中引起的.当我评论这条线
Vector& operator = (const Vector& v) { x = v.x; y = v.y; return *this; }
Run Code Online (Sandbox Code Playgroud)
错误令人失望,当我想保留这一行时,我必须添加以下行:
Vector(Vector&&) = default;
Run Code Online (Sandbox Code Playgroud)
但是,只有当我在Rect类中使用联合和结构时,这个问题才会出现.所以我不知道,我的错误实际上是在哪里造成的,或者只是错误消息指向了错误的类.
重复错误信息:
main.cpp(56): error C2280: 'Rect &Rect::operator =(const Rect &)': attempting to reference a deleted function
这很清楚:operator=带参数的成员函数const Rect &已被deleted,但您的代码尝试在行上调用它test = Rect();。
然后你说:
编译器试图告诉我,类 Rect 的拷贝构造函数是一个被删除的函数
但是,您误读了错误。错误与函数有关,该函数operator =称为复制赋值运算符。这是一个与复制构造函数不同的函数,它看起来像Rect::Rect(const Rect &).
你说你尝试添加:
Rect& operator=(const Rect&) = default;
然而,这不会有什么区别。编译器生成的operator=函数是deleted,因为编译器不可能生成一个(对此的解释如下);写作= default;不会改变这一点。您必须实际编写自己的主体,operator=以便在分配发生时执行您希望发生的操作。
在标准 C++ 中,不允许有匿名结构,更不用说匿名联合中的匿名结构了。所以你真的是一个人在这里。您的编译器使用的关于operator=、复制构造函数等的规则未包含在任何标准中。
Rect可在标准 C 中编译的版本可能如下所示:
class Rect
{
public:
struct S1 {
Vector p1, p2;
S1(Vector p1, Vector p2): p1(p1), p2(p2) {}
};
struct S2 {
float p1x, p1y, p2x, p2y;
};
union {
struct S1 s1;
struct S2 s2;
};
Rect() : s1({0, 0}, {0, 0}) {}
Rect(Vector p1, Vector p2) : s1(p1, p2) {}
};
Run Code Online (Sandbox Code Playgroud)
到现在为止还挺好。对于此类,隐式声明operator=的定义为删除的。要了解原因,我们首先必须查看匿名联合的隐式声明的特殊函数,因为类的隐式声明函数的行为取决于对其每个成员的相同操作的行为。
联合的相关规则是 C++14 [class.union]/1:
如果联合的任何非静态数据成员具有非平凡的默认构造函数、复制构造函数、移动构造函数、复制赋值运算符、移动赋值运算符或析构函数,则联合的相应成员函数必须由用户提供,否则它将
delete为联合隐式为d。
Vector有一个非平凡的operator=,因为你为它写了你自己的身体。因此S1具有 non-trivial operator=,因为它有一个具有 non-trivial 的成员operator=,因此根据上面的引用,operator=为联合隐式声明的是deleted。
请注意,复制构造函数没有错误:Vector 确实有一个简单的复制构造函数,因此联合也有。
要修复此错误,您可以执行以下两项操作之一:
Vector::operator=为微不足道的,要么完全删除您的定义,要么使其成为= default;operator=的Rect类现在,您将如何编写自己的operator=?你做s1 = other.s1;,还是做s2 = other.s2;?编译器不能自己知道,这就是隐式声明operator=被删除的原因。
现在,您似乎忽略了(无意或有意)C++ 中有关活动成员的规则:
在联合中,最多有一个非静态数据成员可以随时处于活动状态
这意味着如果s1是最后一个成员集,那么您必须执行s1 = other.s1;. 或者,如果s2是最后一个成员集,则必须执行s2 = other.s2;.
复制构造函数不会遇到这个问题,因为它是微不足道的:编译器可以生成按位复制,无论哪个成员处于活动状态,它都会正确实现复制。但由于你operator=的不平凡,那是不可能的。
例如,想象一下,如果您实际上有一个std::stringand std::vector- 按位复制的并集对其中任何一个都不起作用,并且您需要知道哪个是活动的才能执行复制。
重申:在标准 C++ 中,除了最近写入. 您不能将联合用于别名。C++ 有其他语言工具来实现您在 C 中可以使用联合别名执行的操作,请参阅此处了解更多讨论。
根据您为匿名结构选择的成员,我怀疑这就是您打算做的。如果您真的想继续使用这种方法,依靠您的编译器将联合别名作为非标准扩展来实现,那么我的建议是operator=为您的Vector类使用默认值。
该错误来自于将浮点数的内存使用p1x, ... p2y置于 Vector 对象的分配之上的联合。
g++ 会给出更明确的错误消息,通知具有构造函数的对象不能在联合中使用。
我很惊讶 VS 没有直接报告在联合中使用对象的错误。如果您在联合中的浮点数之后声明向量,看看会发生什么会很有趣。