我想使用模板来简化具有非平凡类型的联合的构造。以下似乎在实践中“有效”,但在技术上不符合规范:
template<typename T> struct union_entry {
void (*destructor_)(void *); // how to destroy type T when active
T value_;
};
union U {
union_entry<A> a;
union_entry<B> b;
// ... some constructor and destructor...
};
问题是(根据 N4141)只有当两个结构都是标准布局类型时,您才能访问联合中两个结构的公共初始序列(即destructor_
字段)——至少根据 9.5 中的非规范注释.1。根据 9.0.7,标准布局类型不能有任何具有非标准布局的非静态数据成员。因此,如果 A 或 B 不是标准布局,则destructor_
在错误的联合中访问是非法的。
一个漏洞似乎是union_entry
通过转入value_
. alignas(T) char[sizeof(T)]
9.0.7 中似乎没有排除使用alignas
. 因此,我的问题 是:以下是任何类型的标准布局类型T
吗? 因此可以value_
强制转换T&
为模拟前面的示例,同时仍然允许destructor_
在非活动中使用union_entry
?
template<typename T> struct union_entry {
void (*destructor_)(void *);
alignas(T) char value_[sizeof(T)];
}
在 clang-3.8.1 和 g++-6.2.1 中,std::is_standard_layout
建议union_entry<T>
是标准布局,即使T
不是。这是我想如何使用此技术的完整工作示例:
#include <cassert>
#include <iostream>
#include <new>
#include <string>
using namespace std;
template<typename T> struct union_entry {
void (*destructor_)(void *);
alignas(T) char value_[sizeof(T)];
union_entry() : destructor_(nullptr) {}
~union_entry() {} // Just to cause error in unions w/o destructors
void select() {
if (destructor_)
destructor_(this);
destructor_ = destroy_helper;
new (static_cast<void *>(value_)) T{};
}
T &get() {
assert(destructor_ == destroy_helper);
return *reinterpret_cast<T *>(value_);
}
private:
static void destroy_helper(void *_p) {
union_entry *p = static_cast<union_entry *>(_p);
p->get().~T();
p->destructor_ = nullptr;
}
};
union U {
union_entry<int> i;
union_entry<string> s;
U() : i() {}
~U() { if (i.destructor_) i.destructor_(this); }
};
int
main()
{
U u;
u.i.select();
u.i.get() = 5;
cout << u.i.get() << endl;
u.s.select();
u.s.get() = "hello";
cout << u.s.get() << endl;
// Notice that the string in u.s is destroyed by calling
// u.i.destructor_, not u.s.destructor_
}