如何使用两种类型的联合体

10

我正在尝试创建一个可以存储 stringintvector

我已经尝试了下面的代码,但是我得到了编译错误:

error: use of deleted function 'my_union::~my_union()'

我做错了什么?

#include <iostream>
#include <vector>

using namespace std;

union my_union
{
    string str;
    int a;
};

int main() 
{
    vector<my_union> v;
    my_union u;         // error: use of deleted function 'my_union::~my_union()'
    u.str = "foo";
    v.push_back(u);
    return 0;
}

1
@PeteBecker 我明白,但是这个问题不是在 Q 的 这个 回答中已经解决了吗? - NathanOliver
2
你打算如何“知道”以后从向量中读取联合体的哪个成员? - M.M
@M.M 这是一个好问题。可能的字符串只有很少几个,所以我打算检查字符串成员是否与其中之一相等,如果没有匹配,则必须是整数。想一想,这听起来像是一个可怕且容易出错的想法。 - cpp beginner
1
@cppbeginner 这是不可行的。int 重叠了 std::string 对象的初始部分。因此,如果实际上在其中有一个 int,那么 std::string 的表示形式在内部将会非常损坏,并且访问它可能会给您带来非常奇怪的结果或崩溃。从正式上讲,访问联合体的非活动成员是未定义行为。 - Angew is no longer proud of SO
3
你可能想要了解 boost::variant - Angew is no longer proud of SO
显示剩余4条评论
3个回答

14

来源于这里

如果一个union包含带有非平凡特殊成员函数(默认构造函数、复制/移动构造函数、复制/移动赋值运算符或析构函数)的非静态数据成员,则该函数在union中默认被删除,并且需要程序员显式地定义。

为了替换因string自动删除而产生的析构函数,您必须显式定义一个析构函数。

还要注意,这仅适用于c++11。在早期版本中,您根本不能在union内拥有具有非平凡特殊成员函数的类型。

从实际角度来看,这可能仍然不是一个好主意。


4
“可能不是一个好主意”-确实。当您使用具有非平凡构造函数或析构函数的类型时,您必须使用放置new和显式析构函数调用来将不同类型的对象存储在联合中。 - Pete Becker
1
@CodesInChaos 这个问题的评论提到了boost::variant。我自己没有使用过,但从网页上的描述来看,它似乎非常适合,甚至给出了与问题完全相同的例子。 - Rotem

2

当你在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));
  }
  // const versions go here
private:
  // a small magic switch:
  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{}; // "leaks" but we don't care
    });
  }
};

这基本上是一个原始的草图,展示了类似于boost::variant在C++11中的实现方式。

它涉及到一些重要的技术。

上述内容尚未编译,但设计是可靠的。然而,一些名义上支持C++14的编译器不喜欢在完整的lambda周围进行pack扩展,这将需要更多的样板文件。


1
在 C++11 之前,不允许在 union 中使用 std::string,如 此处 引用所述:

联合体不能包含具有非平凡特殊成员函数(复制构造函数、复制赋值运算符或析构函数)的非静态数据成员。

自 C++11 起,可以在 union 中使用 std::string,如 @Rotem 已回答,需要显式定义 string 的析构函数或显式调用析构函数 explicitly
str.~basic_string<char>();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接