6

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

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. 这段代码有效吗:)?
  2. 每次我们使用布尔值时是否有必要显式调用向量析构函数(如此处所述http://cpp11standard.blogspot.com/2012/11/c11-standard-explained-1-unrestricted.html
  3. 为什么需要一个新的展示位置,而不是只做类似 'array = ms.array' 的事情?

编辑:

  • 是的,它编译
  • “在匿名联合中声明的成员实际上是包含类的成员,并且可以在包含类的构造函数中初始化。” (具有重要成员的 C++11 匿名联合
  • 按照建议添加显式析构函数会导致 SIGSEV 与 g++ 4.8 / clang 4.2
4

2 回答 2

3
  1. 代码的错误:更改array.clear();array.~vector<int>();

说明:在尚未被破坏的对象上operator=使用放置,这可以做任何事情,但实际上您可以期望它泄漏前一个数组一直在使用的动态内存(不释放内存/改变容量,它只是破坏元素和变化)。newclear()size

从 9.5/2 开始:

如果联合的任何非静态数据成员具有非平凡的默认构造函数 (12.1)、复制构造函数 (12.8)、移动构造函数 (12.8)、复制赋值运算符 (12.8)、移动赋值运算符 (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=当现有内存足以容纳所有被复制的元素时,使用会更有效。基本上,您必须匹配构造和破坏。更新:根据您在下面的评论,示例代码有一个错误。

3:为什么需要一个新的展示位置,而不是仅仅做类似 'array = ms.array' 之类的事情?

array = ms.array 调用std::vector<int>::operator=总是假定this指针地址已经正确构造的对象。在该对象内部,您可以期望有一个指针,该指针要么为 NULL,要么指向某个内部短字符串缓冲区,或者指向堆。如果您的对象没有被破坏,那么operator=很可能会在虚假指针上调用内存释放函数。Placement new 表示“忽略该对象将占用的内存的当前内容,并从头开始构造一个具有有效成员的新对象。

于 2013-05-21T05:58:16.720 回答
2

联合不声明默认构造函数、复制构造函数、复制赋值运算符或析构函数。

If std::string declares at least one non-trivial version of a special member function (which is the case), the forementioned ones are all implicitly deleted, and you must declare (and define) them (... if they're used, which is the case).

Insofar, that code isn't correct and should not successfully compile (this is almost-to-the-letter identical to the example in 9.5 par 3 of the standard, except there it's std::string, not std::vector).
(Does not apply for an anon union, as correctly pointed out)

About question (2): In order to safely switch the union, this is necessary, yes. The Standard explicitly says that in 9.5 par 4 [Note].
It makes sense too, if you think about it. At most one data member can be active in a union at any time, and they're not magically default constructed/destroyed, which means you need to properly construct/destruct things. It is not meaningful (or even defined) to use the union as something else otherwise (not that you couldn't do that anyway, but it's undefined).
The object is not a pointer, and you don't know whether it's allocated on the heap either (even if it is allocated on the heap, then it's inside another object, so it's still not allowable to delete it). How do you destroy an object if you can't call delete? How do you allocate an object -- possibly several times -- without leaking if you can't delete it? This doesn't leave many choices. Insofar, the [Note] makes perfect sense.

于 2013-05-21T06:28:31.027 回答