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”等。这有揭露之前的一些错误的效果,最终是一件好事。

但是,我的问题是:

  1. 编译器在2013年调用Guid构造函数,然后在2019年不再调用的原因是什么?我没有更改这些 class 中的任何代码,因此我假设这些是对编译器规则的更改。有什么地方可以让我阅读这方面的内容吗?

  2. 依赖于此默认 2013 行为的案例是错误,我正在追踪它们。但是与此同时,我需要复制 VS2013 构建的默认行为并对联合体进行零初始化,以便可以使用代码。我只是更新了 Attrib.h 的构造函数以调用 vUnion 结构,它具有对所有内容进行零初始化的效果。即:

    #define CONSTRUCT : UsePool(true), OwnPtr(false), 类型(FieldTypeNull), vUnion()

这完全符合我的需要,并且对于经常实例化的 class 似乎没有明显的额外成本。但是有更好的方法吗?

首先,由于 Guid 有一个构造函数,包含这样一个 Guid 的联合是一个“无限制联合”。在 C++98 中,这甚至是不允许的。此外,union 是匿名的。这是一个有问题的组合:不受限制的联合通常需要特殊的成员函数,但匿名联合不能有那些特殊的成员函数。

您误以为调用了“Guid 构造函数”。不是;联合未初始化。读取未初始化的值是未定义的行为,任何事情都可能发生。 “0”只是众多可能结果中的一个。

结果可能因月相而改变。一个新的编译器更有可能带来它自己的随机新结果。没有什么特别值得阅读的;未定义行为未定义。这就是它的工作原理。如果有什么可读的,那将是实现定义的行为。

执行此操作的“更好方法”称为 std::variant。不再需要重新发明轮子。这些具有非平凡类型的匿名联合很难正确处理,替代方案就在标准库中