当你在C++11中使用一个不基本上是Plain Old Data(POD)的类创建一个union时,它虽然会允许你这样做,但它却会隐式删除大多数特殊成员函数,如析构函数。
union my_union
{
string str;
int a;
};
实际问题在于,在C++销毁联合体时,它不知道上述哪些部分是有效的。可以通过使用带标签的联合体来解决此问题,并跟踪哪个联合体是活动的,在那种情况下手动进行销毁。因此,我们可以得到以下内容:
struct tagged_union {
enum active {nothing, string, integer} which_active;
template<active...As>
using actives = std::integral_sequence<active, As...>
using my_actives = actives<nothing, string, integer>;
struct nothingness {};
union my_union
{
nothingness nothing;
std::string str;
int a;
~my_union() {};
} data;
using my_tuple = std::tuple<nothingness, std::string, int>;
template<active which>
using get_type = std::tuple_element_t<(std::size_t)which, my_tuple>;
template<class F>
void operate_on(F&& f) {
operate_on_internal(my_actives{}, std::forward<F>(f));
}
template<class T, class F>
decltype(auto) operate_on_by_type(F&& f) {
return std::forward<F>(f)(reinterpret_cast<T*>(&data));
}
private:
template<active...As, class F>
void operate_on_internal(actives<As...>, F&& f) {
using ptr = void(*)(my_union*,std::decay_t<F>*);
const ptr table[]={
[](my_union* self, std::decay_t<F>* pf){
std::forward<F>(*pf)(*(get_type<As>*)self);
}...,
nullptr
};
table[which](&data, std::address_of(f));
}
public:
template<class...Args>
tagged_union(Active w, Args&&...args) {
operate_on([&](auto& t){
using T = std::decay_t<decltype(t)>();
::new((void*)std::addressof(t)) T(std::forward<Args>(args)...);
which = w;
});
}
tagged_union():tagged_union(nothing){}
~tagged_union() {
operate_on([](auto& t){
using T = std::decay_t<decltype(t)>();
t->~T();
which=nothing;
::new((void*)std::addressof(t)) nothingness{};
});
}
};
这基本上是一个原始的草图,展示了类似于boost::variant
在C++11中的实现方式。
它涉及到一些重要的技术。
上述内容尚未编译,但设计是可靠的。然而,一些名义上支持C++14的编译器不喜欢在完整的lambda周围进行pack扩展,这将需要更多的样板文件。
int
重叠了std::string
对象的初始部分。因此,如果实际上在其中有一个int
,那么std::string
的表示形式在内部将会非常损坏,并且访问它可能会给您带来非常奇怪的结果或崩溃。从正式上讲,访问联合体的非活动成员是未定义行为。 - Angew is no longer proud of SOboost::variant
。 - Angew is no longer proud of SO