Class 中联合体的默认初始化
Default Initialization of a Union within a Class
我正在将一些遗留的 c++ 代码从 Visual Studio 2013 更新到 VS2019,并遇到了一个有趣的问题:
有问题的代码使用名为 Attrib.h 的 class 来表示 table 单元格中任何可能的数据单元。此数据可以是 bool、int、double、Guid/Uuid、各种类型的几何数据、文本等。因此,Attrib.h 包含许多不同结构的联合,用于表示这些不同的可能数据类型.我在下面包含了一个删节版本:
class Attrib
{
public:
union
{
double vDouble;
bool vBool;
struct sUnion
{
#ifdef _WIN64
BYTE Data[24];
#else
BYTE Data[16];
#endif
} vUnion;
union
{
struct
{
INT32 vInt32;
INT32 vInt32High;
};
INT64 vInt64;
};
struct sStr
{
//etc
} vStr;
struct sBin
{
//etc
} vBin;
struct sGeomPt
{
//etc
} vGeomPt;
struct sGeomMultiPt
{
//etc
} vGeomMultiPt;
struct sGeomPoly
{
//etc
} vGeomPoly;
struct
{
Guid vGuid;
};
};
bool UsePool : 1;
bool OwnPtr : 1;
FieldType Type : 8;
//Other data members, excluded for brevity.
#define CONSTRUCT : UsePool(true), OwnPtr(false), Type(FieldTypeNull)
Attrib() CONSTRUCT
{}
}
现在......由于我不明白的原因,每当在 VS2013 中创建 Attrib 实例(例如 Attrib myAttrib)时,编译器将调用 class Guid 的构造函数(最后一个联盟成员)。 Guids构造函数如下:
Guid()
{
Q1 = 0;
Q2 = 0;
}
Q1 和 Q2 是无符号 64 位整数,并且是 Class Guid(Microsoft GUID 的变体)的唯一成员。这具有将 Class Attrib 的联合初始化为 0 的效果,这反过来最终掩盖了许多错误。长话短说,未初始化的属性应该是 Null 类型。但在一些地方,由于初始化为零,它们被错误地解释为值为零的 double 或 int,或者值为 false 的 bool 等。
但是在切换到 VS2019 时,从未调用 Guid 构造函数(其余代码未更改,因此我认为这是编译器的“决定”)。结果,Attrib class 的 Union 组件未初始化为 0,而是初始化为大量负数(在整数或双精度的情况下),在 bool 的情况下为“true”等。这有揭露之前的一些错误的效果,最终是一件好事。
但是,我的问题是:
编译器在2013年调用Guid构造函数,然后在2019年不再调用的原因是什么?我没有更改这些 class 中的任何代码,因此我假设这些是对编译器规则的更改。有什么地方可以让我阅读这方面的内容吗?
依赖于此默认 2013 行为的案例是错误,我正在追踪它们。但是与此同时,我需要复制 VS2013 构建的默认行为并对联合体进行零初始化,以便可以使用代码。我只是更新了 Attrib.h 的构造函数以调用 vUnion 结构,它具有对所有内容进行零初始化的效果。即:
#define CONSTRUCT : UsePool(true), OwnPtr(false), 类型(FieldTypeNull), vUnion()
这完全符合我的需要,并且对于经常实例化的 class 似乎没有明显的额外成本。但是有更好的方法吗?
首先,由于 Guid
有一个构造函数,包含这样一个 Guid
的联合是一个“无限制联合”。在 C++98 中,这甚至是不允许的。此外,union
是匿名的。这是一个有问题的组合:不受限制的联合通常需要特殊的成员函数,但匿名联合不能有那些特殊的成员函数。
您误以为调用了“Guid 构造函数”。不是;联合未初始化。读取未初始化的值是未定义的行为,任何事情都可能发生。 “0”只是众多可能结果中的一个。
结果可能因月相而改变。一个新的编译器更有可能带来它自己的随机新结果。没有什么特别值得阅读的;未定义行为未定义。这就是它的工作原理。如果有什么可读的,那将是实现定义的行为。
执行此操作的“更好方法”称为 std::variant
。不再需要重新发明轮子。这些具有非平凡类型的匿名联合很难正确处理,替代方案就在标准库中
我正在将一些遗留的 c++ 代码从 Visual Studio 2013 更新到 VS2019,并遇到了一个有趣的问题:
有问题的代码使用名为 Attrib.h 的 class 来表示 table 单元格中任何可能的数据单元。此数据可以是 bool、int、double、Guid/Uuid、各种类型的几何数据、文本等。因此,Attrib.h 包含许多不同结构的联合,用于表示这些不同的可能数据类型.我在下面包含了一个删节版本:
class Attrib
{
public:
union
{
double vDouble;
bool vBool;
struct sUnion
{
#ifdef _WIN64
BYTE Data[24];
#else
BYTE Data[16];
#endif
} vUnion;
union
{
struct
{
INT32 vInt32;
INT32 vInt32High;
};
INT64 vInt64;
};
struct sStr
{
//etc
} vStr;
struct sBin
{
//etc
} vBin;
struct sGeomPt
{
//etc
} vGeomPt;
struct sGeomMultiPt
{
//etc
} vGeomMultiPt;
struct sGeomPoly
{
//etc
} vGeomPoly;
struct
{
Guid vGuid;
};
};
bool UsePool : 1;
bool OwnPtr : 1;
FieldType Type : 8;
//Other data members, excluded for brevity.
#define CONSTRUCT : UsePool(true), OwnPtr(false), Type(FieldTypeNull)
Attrib() CONSTRUCT
{}
}
现在......由于我不明白的原因,每当在 VS2013 中创建 Attrib 实例(例如 Attrib myAttrib)时,编译器将调用 class Guid 的构造函数(最后一个联盟成员)。 Guids构造函数如下:
Guid()
{
Q1 = 0;
Q2 = 0;
}
Q1 和 Q2 是无符号 64 位整数,并且是 Class Guid(Microsoft GUID 的变体)的唯一成员。这具有将 Class Attrib 的联合初始化为 0 的效果,这反过来最终掩盖了许多错误。长话短说,未初始化的属性应该是 Null 类型。但在一些地方,由于初始化为零,它们被错误地解释为值为零的 double 或 int,或者值为 false 的 bool 等。
但是在切换到 VS2019 时,从未调用 Guid 构造函数(其余代码未更改,因此我认为这是编译器的“决定”)。结果,Attrib class 的 Union 组件未初始化为 0,而是初始化为大量负数(在整数或双精度的情况下),在 bool 的情况下为“true”等。这有揭露之前的一些错误的效果,最终是一件好事。
但是,我的问题是:
编译器在2013年调用Guid构造函数,然后在2019年不再调用的原因是什么?我没有更改这些 class 中的任何代码,因此我假设这些是对编译器规则的更改。有什么地方可以让我阅读这方面的内容吗?
依赖于此默认 2013 行为的案例是错误,我正在追踪它们。但是与此同时,我需要复制 VS2013 构建的默认行为并对联合体进行零初始化,以便可以使用代码。我只是更新了 Attrib.h 的构造函数以调用 vUnion 结构,它具有对所有内容进行零初始化的效果。即:
#define CONSTRUCT : UsePool(true), OwnPtr(false), 类型(FieldTypeNull), vUnion()
这完全符合我的需要,并且对于经常实例化的 class 似乎没有明显的额外成本。但是有更好的方法吗?
首先,由于 Guid
有一个构造函数,包含这样一个 Guid
的联合是一个“无限制联合”。在 C++98 中,这甚至是不允许的。此外,union
是匿名的。这是一个有问题的组合:不受限制的联合通常需要特殊的成员函数,但匿名联合不能有那些特殊的成员函数。
您误以为调用了“Guid 构造函数”。不是;联合未初始化。读取未初始化的值是未定义的行为,任何事情都可能发生。 “0”只是众多可能结果中的一个。
结果可能因月相而改变。一个新的编译器更有可能带来它自己的随机新结果。没有什么特别值得阅读的;未定义行为未定义。这就是它的工作原理。如果有什么可读的,那将是实现定义的行为。
执行此操作的“更好方法”称为 std::variant
。不再需要重新发明轮子。这些具有非平凡类型的匿名联合很难正确处理,替代方案就在标准库中