不受限制的工会

Unrestricted union in practice

本文关键字:受限制      更新时间:2023-10-16

我对无限制联合及其在实践中的应用有一些疑问。假设我有以下代码:

struct MyStruct
{
    MyStruct(const std::vector<int>& a) : array(a), type(ARRAY)
    {}
    MyStruct(bool b) : boolean(b), type(BOOL)
    {}
    MyStruct(const MyStruct& ms) : type(ms.type)
    {
        if (type == ARRAY)
            new (&array) std::vector<int>(ms.array);
        else
            boolean = ms.boolean;
    }
    MyStruct& operator=(const MyStruct& ms)
    {
        if (&ms != this) {
            if (type == ARRAY)
                array.~vector<int>(); // EDIT(2) 
            if (ms.type == ARRAY)
                new (&array) std::vector<int>(ms.array);
            else
                boolean = ms.boolean;
            type = ms.type;
        }
        return *this;
    }
    ~MyStruct()
    {
        if (type == ARRAY)
            array.~vector<int>();
    }
    union {
        std::vector<int> array;
        bool             boolean;
    };
    enum {ARRAY, BOOL} type;
};
    这个代码是有效的吗?
  1. 是否有必要在每次使用布尔值时显式调用向量析构函数(如这里所述http://cpp11standard.blogspot.com/2012/11/c11-standard-explained-1-unrestricted.html)
  2. 为什么需要一个新的位置,而不是只是做一些像'array = ms.array' ?
编辑:

  • 是的,它编译
  • "在匿名联合中声明的成员实际上是包含类的成员,并且可以在包含类的构造函数中初始化。"(c++ 11具有非平凡成员的匿名联合)
  • 按照建议添加显式析构函数,导致g++ 4.8/clang 4.2的SIGSEV
  1. 代码错误:将array.clear();更改为array.~vector<int>();

解释:operator=使用放置new在一个没有被破坏的对象上,这可以做任何事情,但实际上你可以预期它会泄漏以前数组已经使用的动态内存(clear()不释放内存/改变容量,它只是破坏元素并改变size)。

从9.5/2:

如果联合的任何非静态数据成员具有非平凡默认值构造函数(12.1),复制构造函数(12.8),移动构造函数(12.8),复制赋值操作符(12.8),move赋值操作符(12.8)或析构函数(12.4),则联合的对应成员函数必须为用户提供,否则将隐式删除(8.4.3)的联合。

因此,vector构造函数、析构函数等永远不会自己启动:你必须在需要时显式调用它们。

在9.5/3中有一个例子:

考虑以下联合:

union U {
    int i;
    float f;
    std::string s;
};

由于std::string(21.3)声明了所有特殊成员函数的非平凡版本,因此U将具有隐式删除的默认构造函数、复制/移动构造函数、复制/移动赋值操作符和析构函数。要使用U,这些成员函数中的一部分或全部必须由用户提供。

最后一位-"要使用U,这些成员函数中的一些或全部必须由用户提供。"-似乎假定U需要协调自己模糊的值语义行为,但在你的情况下,周围的struct正在这样做,所以你不需要定义任何这些union成员函数。

2:当数组值被布尔值替换时,必须调用数组析构函数。如果在operator=中放置一个新的数组值- new而不是赋值,那么旧数组也必须调用其析构函数,但是当现有内存足以容纳所有被复制的元素时,使用operator=将更有效。基本上,您必须匹配构造和析构。更新:根据你下面的注释,示例代码有一个bug。

3:为什么需要一个新的位置,而不是像'array = ms.array'这样的东西?

array = ms.array调用std::vector<int>::operator=,它总是假设this指针指向一个已经正确构造的对象。在该对象中,你可以期望有一个指针,它要么为NULL,要么引用一些内部短字符串缓冲区,要么引用堆。如果您的对象没有被销毁,那么operator=可能会在伪指针上调用内存释放函数。放置new表示"忽略该对象将占用的当前内存内容,并从头构造一个包含有效成员的新对象。

联合没有声明默认构造函数、复制构造函数、复制赋值操作符或析构函数。

如果std::string声明了至少一个特殊成员函数的非平凡版本(这是事实),那么前面提到的那些都将被隐式删除,您必须声明(并定义)它们(…)

到目前为止,这段代码是不正确的,不应该成功编译(这几乎与标准的9.5 par 3中的示例完全相同,除了它是std::string,而不是std::vector)。
(不申请匿名联合,正如正确指出的那样)

关于问题(2):为了安全切换联合,这是必要的,是的。标准明确指出,在9.5 par 4中[注]。
如果你仔细想想,这也是有道理的。在任何时候,union中最多只能有一个数据成员是活动的,并且它们不会神奇地默认构造/销毁,这意味着您需要正确地构造/销毁东西。使用union作为其他东西是没有意义的(甚至没有定义)(不是说你不能这样做,但它是未定义的)。
该对象不是指针,您也不知道它是否在堆上分配(即使它在堆上分配,那么它也在另一个对象内,因此仍然不允许删除它)。如果不能调用delete,如何销毁对象?如果不能删除对象,如何分配对象(可能是多次)而不泄漏?这样就没有多少选择了。到目前为止,[Note]是完全有意义的。