当我尝试在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;
}
我收到以下错误消息:
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;
我认识到错误实际上并不是在Rect类中引起的。当我评论该行
时Vector& operator = (const Vector& v) { x = v.x; y = v.y; return *this; }
错误令人失望,当我想保留这一行时,我必须添加以下行:
Vector(Vector&&) = default;
然而,只有当我在Rect类中使用联合和结构时,这个问题才会出现。 所以我不知道,我的错误实际上是在哪里造成的,或者只是错误消息指向了错误的类。
答案 0 :(得分:4)
重复错误讯息:
main.cpp(56): error C2280: 'Rect &Rect::operator =(const Rect &)': attempting to reference a deleted function
这很清楚:参数operator=
的成员函数const Rect &
已delete
d,但您的代码会尝试在test = Rect();
行上调用它。
然后你说:
编译器试图告诉我,类Rect的复制构造函数是一个已删除的函数
但是,您误读了错误。错误与函数operator =
有关,该函数称为复制赋值运算符。这是与复制构造函数不同的函数,它看起来像Rect::Rect(const Rect &)
。
你说你试过添加:
Rect& operator=(const Rect&) = default;
然而,这没有任何区别。编译器生成的operator=
函数是delete
d因为编译器不可能生成一个函数(下面对此进行解释);写= default;
并不会改变这一点。你必须为operator=
编写自己的正文,它执行你在分配发生时想要发生的动作。
在标准C ++中,不允许使用匿名结构,更不用说匿名结合中的匿名结构。所以你真的在这里独自一人。编译器使用的有关operator=
,复制构造函数等的规则不受任何标准的约束。
在标准C中可编译的Rect
版本可能如下所示:
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) {}
};
到目前为止,这么好。对于此类,隐式声明的operator=
定义为已删除。为了了解原因,我们首先要查看匿名联合的隐式声明的特殊函数,因为类的隐式声明函数的行为取决于每个成员的相同操作的行为。
此处关联的相关规则是C ++ 14 [class.union] / 1:
如果联合的任何非静态数据成员具有非平凡的默认构造函数,复制构造函数,移动构造函数,复制赋值运算符,移动赋值运算符或析构函数,则联合的相应成员函数必须是用户提供的或者它将隐含
delete
d用于联盟。
Vector
有一个非平凡的operator=
,因为你为自己编写了自己的身体。因此,S1
具有非平凡的operator=
,因为它有一个非平凡的operator=
成员,因此根据上面的引用,隐式声明operator=
联盟是delete
d。
请注意,复制构造函数没有错误: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::string
和std::vector
的联合 - 按位复制并不适用于其中任何一个,你需要知道哪一个是活动的,以便执行复制。
重申:在标准C ++中,不允许读取最近写入的联盟成员。您无法使用联合进行别名。 C ++有其他语言工具可以实现你在C中使用联合别名see here for more discussion。
所做的事情根据您对匿名结构的成员选择,我怀疑这是您打算做的。如果你真的想继续这种方法,依靠你的编译器实现联合别名作为非标准的扩展,那么我的建议是使用默认的operator=
作为你的Vector
类。
答案 1 :(得分:1)
错误来自联合,将浮点数p1x, ... p2y
的内存使用量放在Vector对象的分配之上。
g ++会给出一个更明确的错误消息,告知具有构造函数的对象不能在联合中使用。
我很惊讶VS没有直接报告在联合中使用对象的错误。如果你在你的联盟中宣布浮动之后会发生什么,那将会很有趣。