具有非平凡成员的 C++11 匿名联合

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10693913/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:20:55  来源:igfitidea点击:

C++11 anonymous union with non-trivial members

c++constructorc++11anonymousunions

提问by OmnipotentEntity

I'm updating a struct of mine and I was wanting to add a std::string member to it. The original struct looks like this:

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

struct Value {
  uint64_t lastUpdated;

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

Just adding a std::string member to the union, of course, causes a compile error, because one would normally need to add the non-trivial constructors of the object. In the case of std::string (text from informit.com)

当然,仅仅向联合添加一个 std::string 成员会导致编译错误,因为通常需要添加对象的非平凡构造函数。 对于 std::string(来自informit.com 的文本)

Since std::string defines all of the six special member functions, U will have an implicitly deleted default constructor, copy constructor, copy assignment operator, move constructor, move assignment operator and destructor. Effectively, this means that you can't create instances of U unless you define some, or all of the special member functions explicitly.

由于 std::string 定义了所有六个特殊成员函数,U 将具有隐式删除的默认构造函数、复制构造函数、复制赋值运算符、移动构造函数、移动赋值运算符和析构函数。实际上,这意味着您不能创建 U 的实例,除非您明确定义了部分或全部特殊成员函数。

Then the website goes on to give the following sample code:

然后该网站继续提供以下示例代码:

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

However, I'm using an anonymous union within a struct. I asked ##C++ on freenode and they told me the correct way to do that was to put the constructor in the struct instead and gave me this example code:

但是,我在结构中使用匿名联合。我在 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)
{
}

But from there I can't figure how to make the rest of the special functions that std::string needs defined, and moreover, I'm not entirely clear on how the ctor in that example is working.

但是从那里我无法弄清楚如何定义 std::string 需要定义的其余特殊函数,而且,我并不完全清楚该示例中的 ctor 是如何工作的。

Can I get someone to explain this to me a bit clearer?

我可以找人给我解释得更清楚一点吗?

采纳答案by Ben Voigt

There is no need for placement new here.

这里不需要放置新的。

Variant members won't be initialized by the compiler-generated constructor, but there should be no trouble picking one and initializing it using the normal ctor-initializer-list. Members declared inside anonymous unions are actually members of the containing class, and can be initialized in the containing class's constructor.

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

This behavior is described in section 9.5. [class.union]:

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

A union-likeclass is a union or a class that has an anonymous union as a direct member. A union-like class Xhas a set of variant members. If Xis a union its variant members are the non-static data members; otherwise, its variant members are the non-static data members of all anonymous unions that are members of X.

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

and in section 12.6.2 [class.base.init]:

并在第 12.6.2 节中[class.base.init]

A ctor-initializermay initialize a variant member of the constructor's class. If a ctor-initializerspecifies more than one mem-initializerfor the same member or for the same base class, the ctor-initializeris ill-formed.

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

So the code can be simply:

所以代码可以很简单:

#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)
{
}

Of course, placement new should still be used when vivifying a variant member other than the other initialized in the constructor.

当然,在使一个变体成员而不是在构造函数中初始化的另一个变体成员时,仍然应该使用placement new。

回答by Luc Danton

That new (&p) Point()example is a call to the Standard placement newoperator (via a placement new expression), hence why you need to include <new>. That particular operator is special in that it does notallocate memory, it only returns what you passed to it (in this case it's the &pparameter). The net result of the expression is that an object has been constructed.

new (&p) Point()示例是对标准放置new运算符的调用(通过放置 new 表达式),因此您需要包含<new>. 这个特定的运营商是特殊的,它并没有分配内存,它只返回你传递什么给它(在这种情况下,它的&p参数)。该表达式的最终结果是已经构造了一个对象。

If you combine this syntax with explicit destructor calls then you can achieve complete control over the lifetime of an object:

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

// 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();

When and where you should construct and destroy the std::stringmember (let's call it s) in your Valueclass depends on your usage pattern for s. In this minimal example you never construct (and hence destruct) it in the special members:

您应该在何时何地构造和销毁类中的std::string成员(我们称之为sValue取决于您的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;
    };
};

The following is thus a valid use of Value:

因此,以下是有效的使用Value

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

As you may have noticed, I disabled copying and moving Value. The reason for that is that we can't copy or move the appropriate active member of the union without knowing which one it is that is active, if any.

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