C++11 与非平凡成员的匿名联盟

C++11 anonymous union with non-trivial members

本文关键字:联盟 成员 C++11      更新时间:2023-10-16

我正在更新我的一个结构,我想添加一个 std::string 成员。 原始结构如下所示:

struct Value {
  uint64_t lastUpdated;
  union {
    uint64_t ui;
    int64_t i;
    float f;
    bool b;
  };
};

当然,只需将 std::string 成员添加到联合中就会导致编译错误,因为通常需要添加对象的非平凡构造函数。 在 std::string 的情况下(来自 informit.com 的文本(

由于 std::string 定义了所有六个特殊成员函数,因此您将有一个隐式删除的默认构造函数、复制构造函数、复制赋值运算符、移动构造函数、移动赋值运算符和析构函数。实际上,这意味着除非显式定义某些或所有特殊成员函数,否则无法创建 U 的实例。

然后网站继续给出以下示例代码:

union U
{
int a;
int b;
string s;
U();
~U();
};

但是,我在结构中使用匿名联合。 我在freenode上问##C++,他们告诉我正确的方法是将构造函数放在结构中,并给了我这个示例代码:

#include <new>
struct Point  {
    Point() {}
    Point(int x, int y): x_(x), y_(y) {}
    int x_, y_;
};
struct Foo
{
  Foo() { new(&p) Point(); }
  union {
    int z;
    double w;
    Point p;
  };
};
int main(void)
{
}

但是从那里我不知道如何定义 std::string 需要定义的其余特殊函数,此外,我不完全清楚该示例中的 ctor 是如何工作的。

我能不能找人把这件事解释得更清楚一点?

这里不需要放置新的位置。

编译器生成的构造函数不会初始化变体成员,但选择一个变量并使用普通的 ctor-initializer-list 初始化它应该没有问题。 在匿名联合中声明的成员实际上是包含类的成员,并且可以在包含类的构造函数中初始化。

第 9.5 节中描述了此行为。 [class.union]

类似工会的阶级

是一个工会或以匿名工会作为直接成员的阶级。 类似联合的类X具有一组变体成员。如果X是一个联合,则其变体成员是非静态数据成员;否则,其变体成员是作为 X 成员的所有匿名联合的非静态数据成员。

在第 12.6.2 节[class.base.init]

ctor-initializer 可以初始化构造函数类的变体成员。如果 ctor-initializer 为同一成员或同一基类指定了多个 mem 初始值设定项,则 ctor-initializer 的格式不正确。

所以代码可以简单地:

#include <new>
struct Point  {
    Point() {}
    Point(int x, int y): x_(x), y_(y) {}
    int x_, y_;
};
struct Foo
{
  Foo() : p() {} // usual everyday initialization in the ctor-initializer
  union {
    int z;
    double w;
    Point p;
  };
};
int main(void)
{
}

当然,在激活构造函数中初始化的其他变体成员之外的变体成员时,仍应使用放置 new。

new (&p) Point()示例是对标准放置new运算符的调用(通过放置新表达式(,因此您需要包含<new> .该特定运算符的特殊之处在于它分配内存,它只返回您传递给它的内容(在本例中为 &p 参数(。表达式的最终结果是已构造一个对象。

如果将此语法与显式析构函数调用结合使用,则可以完全控制对象的生存期:

// Let's assume storage_type is a type
// that is appropriate for our purposes
storage_type storage;
std::string* p = new (&storage) std::string;
// p now points to an std::string that resides in our storage
// it was default constructed
// *p can now be used like any other string
*p = "foo";
// Needed to get around a quirk of the language
using string_type = std::string;
// We now explicitly destroy it:
p->~string_type();
// Not possible:
// p->~std::string();
// This did nothing to our storage however
// We can even reuse it
p = new (&storage) std::string("foo");
// Let's not forget to destroy our newest object
p->~string_type();

您应该在Value类中构造和销毁std::string成员(我们称之为s(的时间和地点取决于您对s的使用模式。在这个最小的示例中,您永远不会在特殊成员中构造(因此破坏(它:

struct Value {
    Value() {}
    Value(Value const&) = delete;
    Value& operator=(Value const&) = delete;
    Value(Value&&) = delete;
    Value& operator=(Value&&) = delete;
    ~Value() {}
    uint64_t lastUpdated;
    union {
        uint64_t ui;
        int64_t i;
        float f;
        bool b;
        std::string s;
    };
};

因此,以下是Value的有效用法:

Value v;
new (&v.s) std::string("foo");
something_taking_a_string(v.s);
using string_type = std::string;
v.s.~string_type();

您可能已经注意到,我禁用了复制和移动Value。这样做的原因是,我们无法复制或移动工会的适当活跃成员,而不知道哪个成员是活跃的(如果有的话(。