【问题标题】:Unrestricted union in practice实践中的无限制联合
【发布时间】:2013-05-21 16:48:24
【问题描述】:

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

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 anonymous union with non-trivial members)
  • 按照建议添加显式析构函数会导致使用 g++ 4.8 / clang 4.2 的 SIGSEV

【问题讨论】:

  • 联合有时被用来颠覆类型系统——类型双关语。在这种情况下,您不会在读/写bool 之前破坏vector,但标准没有(也不能)定义这意味着什么——效果取决于平台的细节。无论如何,您可能不会使用 vector 来执行此操作 - 也许使用 char 数组来查看字节。如果您没有双关语,则只能读出最后写入的内容(否则会导致数据损坏),并且您必须在写入其他内容之前删除(破坏)联合中已经存在的任何内容(否则会导致内存/资源泄漏) .

标签: c++ c++11 unions


【解决方案1】:
  1. 代码有问题:将 array.clear(); 更改为 array.~vector&lt;int&gt;();

解释:operator= 正在将 new 放置在尚未被破坏的对象上,这可以做任何事情,但实际上您可以预期它会泄漏前一个数组一直使用的动态内存(clear() 不会'不释放内存/改变容量,它只是破坏元素并改变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= 中,一个新的数组值被放置-newed 而不是分配,那么旧数组也必须调用它的析构函数,但是当现有内存足够时使用operator= 会更有效被复制的元素。基本上,您必须匹配构造和破坏。更新:根据您的评论,示例代码有一个错误。

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

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

【讨论】:

  • 在布尔构造函数/复制构造函数中显式破坏向量会导致 SIGSEV :( 我在这里遗漏了一些东西。关于匿名联合成员,标准似乎允许(参见stackoverflow.com/questions/10693913/…)跨度>
  • @3XX0:抱歉 - 维基百科的 C++11 文章 (en.wikipedia.org/wiki/C%2B%2B11#Unrestricted_unions) 在“// 由于 Point 成员,现在需要构造函数定义”处有点误导。 - 如果不检查标准,我不应该相信它。已经相应地更新了我的答案,并且使用上面引用的标准,复制构造函数不应该调用向量析构函数 - 对不起这个流浪汉。
  • @3XX0:我上面链接中提到的 Wikipedia 文章与我的文章中讨论的 9.5/3 一致 - 我想说这两种情况都具有误导性。
  • 感谢您的更新。事实上,我忽略了赋值运算符中的析构函数(我的错)。但是,以下示例(来自文章)在构造字符串时删除了向量。用我的例子这样做似乎不是正确的做法(SIGSEV)str_int(std::string str) { _raw.~vector&lt;char&gt;(); new (&amp;str) std::string(str); }
  • @3XX0: 上面文章中的例子是一个构造函数...根据上面引用的 9.5/2,std::string 成员不会被构造,因为联合的构造函数被隐式删除,所以构造函数不应该调用析构函数;博客示例代码是错误的。只需使用展示位置new
【解决方案2】:

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

如果std::string 至少声明了一个特殊成员函数的非平凡版本(就是这种情况),则前面提到的都被隐式删除,您必须声明(并定义)它们(...如果它们'被使用,就是这样)。

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

关于问题(2):为了安全切换union,这个是必须的,是的。该标准在 9.5 par 4 [注] 中明确指出。
如果您考虑一下,这也很有意义。最多一个数据成员可以随时在union 中处于活动状态,并且它们不会神奇地默认构造/销毁,这意味着您需要正确构造/销毁事物。将union 用作其他东西是没有意义的(甚至没有定义)(不是你不能这样做,但它是未定义的)。
对象不是指针,也不知道是不是分配在堆上(即使分配在堆上,那它里面是另一个对象,所以还是不能删除它)。如果不能调用delete,如何销毁对象?如果你不能删除它,你如何分配一个对象——可能多次——而不泄漏?这并没有留下很多选择。就目前而言,[Note] 非常有意义。

【讨论】:

  • 由于它是一个匿名联合,因此允许使用类设施 (stackoverflow.com/questions/10693913/…) 关于您的回答 (2) 我完全同意,但破坏向量会导致 SIGSEV ...来自节点段错误的最后一个代码 sn-p (clang 3.2 / g++ 4.8)
猜你喜欢
  • 2014-02-28
  • 1970-01-01
  • 2010-09-21
  • 2021-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-09
相关资源
最近更新 更多