我正在更新我的一个结构,我想向其中添加一个 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 将具有隐式删除的默认构造函数、复制构造函数、复制赋值运算符、移动构造函数、移动赋值运算符和析构函数。实际上,这意味着除非显式定义一些或全部特殊成员函数,否则您无法创建 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]
:
类union类是一个联合或具有匿名联合作为直接成员的类。类似联合的类
有一组变体成员。如果X
是联合体,则其变体成员是非静态数据成员;否则,其变体成员是属于X
成员的所有匿名联合的非静态数据成员。X
以及第 12.6.2 节中
[class.base.init]
:
A ctor-initializer 可以初始化构造函数类的变体成员。如果 ctor-initializer 为同一成员或同一基类指定多个 mem-initializer,则 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();
何时何地应该在
std::string
类中构造和销毁 s
成员(我们称之为 Value
)取决于您对 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
。原因是,如果不知道哪个活跃成员(如果有的话),我们就无法复制或移动联盟中适当的活跃成员。