Как использовать объединение двух типов

Я пытаюсь сделать vector, который может содержать string и int.

Я пробовал код ниже, но я получаю ошибку компиляции

ошибка: использование удаленной функции 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

Из здесь

Если объединение содержит нестатический элемент данных с нетривиальной специальной функцией-членом (конструктор по умолчанию, конструктор копирования/перемещения, назначение копирования/перемещения или деструктор), эта функция по умолчанию удаляется в объединении и требует который будет определен явно программистом.

Вы должны явно определить деструктор для вашего объединения, чтобы заменить тот, который был автоматически удален для string.

Также обратите внимание, что это допустимо только в С++ 11. В более ранних версиях у вас не может быть типа с нетривиальными специальными функциями-членами внутри объединения.

С практической точки зрения, это все еще не может быть отличной идеей.

Ответ 2

Когда вы создаете объединение с классом, который не является в основном простыми старыми данными, в С++ 11 он позволяет вам. Но он идет и неявно удаляет большинство специальных функций-членов, таких как деструктор.

union my_union
{
  string str;
  int a;
};

практическая проблема заключается в том, что в точке разрушения С++ не знает, какая из указанных частей союза действительна.

Вы можете обойти это, используя тегированный союз, и отслеживать, который активен, и вручную уничтожить в этом случае.

Итак, мы можем получить что-то вроде:

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, если он написан на С++ 11.

Это связано с тяжелым моджо.

Вышеуказанное не было скомпилировано, но дизайн звучит. Некоторым номинально компиляторам С++ 14 не нравится делать пакет, расширяющийся вокруг полной лямбда, однако это потребует еще большего количества шаблонов.

Ответ 3

До С++ 11 было запрещено использовать std::string в объединении, как указано здесь:

Союзы не могут содержать нестатический элемент данных с нетривиальным специальная функция-член (конструктор копирования, оператор копирования-назначения, или деструктор).

И поскольку С++ 11 вы можете использовать std::string в объединении, как уже ответил @Rotem, вам нужно явно определить деструктор для строки или вызова деструктор явно

str.~basic_string<char>();