实现变体时出现销毁错误

时间:2016-11-04 11:21:23

标签: c++ c++14

我正在尝试重写std::variant的基础,因为我必须使用c ++ 14编译器(没有std :: variant),我需要一个类型安全的枚举。

我的代码仅适用于基本类型(例如intfloatchar *等。)但是当使用std::string时,我遇到了破坏问题。

这是我的代码:

namespace utils
{
  namespace // private implementation
  {
    template<std::size_t Index, typename T, typename... Rest>
    struct can_construct_type : std::false_type {}; // End of recursion, T is not in Rest...

    template<std::size_t Index, typename T, typename First>
    struct can_construct_type<Index, T, First> : std::is_constructible<First, T>
    { // There is only one type remaining to check
      static constexpr std::size_t index = Index;
    };

    template<std::size_t Index, typename T, typename First, typename... Rest>
    struct can_construct_type<Index, T, First, Rest...> : std::integral_constant<bool, std::is_constructible<First, T>::value || can_construct_type<Index + 1, T, Rest...>::value>
    { // Main recursive loop. Check for First, then recursively for Rest...
      static constexpr std::size_t index = (std::is_constructible<First, T>::value ? Index : can_construct_type<Index + 1, T, Rest...>::index);
    };
  }

  template<typename T, typename... Types>
  using can_construct_type = can_construct_type<0, T, Types...>;
}


template<typename... Types>
class variant
{
  template<typename _Vp> friend void * __get_storage(_Vp &);

  using storage_t = std::aligned_union_t<0u, Types...>;
  storage_t m_storage;
  int m_type_index;

  /* creation of content */
  template<typename T> inline void set_value(T const & value)
  {
    new ((T *)std::addressof(m_storage)) T(value);
    m_type_index = utils::can_construct_type<T, Types...>::index;
  }

  /* destruction of content */
  inline void destroy_data(void) { invoke_destructor(m_type_index, std::addressof(m_storage)); }
  static void invoke_destructor(int type, storage_t * storage_address)
  {
    static const std::array<void(*)(storage_t *), sizeof...(Types)> destructors
    {
      std::addressof(invoke_destructor_impl<Types>)...
    };
    destructors[type](storage_address);
  }
  template<class T> static void invoke_destructor_impl(storage_t * storage_address)
  {
    T * pt = reinterpret_cast<T *>(storage_address);
    delete pt;
    //pt->~T(); // I tried delete or ->~T() but both crash
  }

public:
  variant() = delete;
  template<typename T> variant(T value)
  {
    set_value(value);
  }

  template<typename T> variant & operator=(T & value)
  {
    destroy_data();
    set_value(value);
    return *this;
  }

  ~variant()
  {
    destroy_data();
  }

  std::size_t index(void) const
  {
    return m_type_index;
  }
};

/* getter */
template<typename Variant> static void * __get_storage(Variant & var)
{
  return reinterpret_cast<void *>(std::addressof(var.m_storage));
}

template<typename T, typename... Types> T * get_if(variant<Types...> & var)
{
  return (var.index() == utils::contains_type<T, Types...>::index
    ? reinterpret_cast<T *>(__get_storage(var))
    : nullptr);
}

/* tests */
int main()
{
  variant<int, char *> var_test(42);
  int * value1 = get_if<int>(var_test);
  if (value1)
  {
    std::cout << *value1 << "\n"; // OK
  }

  var_test = _strdup("Hello !");
  char ** value2 = get_if<char *>(var_test);
  if (value2)
  {
    std::cout << *value2 << "\n"; // OK
  }

  variant<int, std::string> var_test_2(42);
  //var_test_2 = _strdup("Hello again !");
  var_test_2 = std::string("Hello again !");
  std::string * value3 = get_if<std::string>(var_test_2);
  if (value3)
  {
    std::cout << *value3 << "\n";
  }
  return 0; // it crashes when the destructor of var_test_2 is called
}

我提供了一个完整的代码示例,但只有variant的实现很重要。

我想我很难使用std::aligned_union,但我无法确定在哪里和如何。

我收到错误:

  

Test.exe中0x00096370处抛出异常:0xC0000005:访问冲突写入位置0xDDDDDDD。

更新:似乎析构函数被调用两次,但我找不到原因。

1 个答案:

答案 0 :(得分:3)

您的赋值运算符错误:

template<typename T> variant & operator=(T & value)

这只接受左值,但你尝试分配一个右值:

var_test_2 = std::string("Hello again !");

它不是调用自定义赋值运算符,而是通过非variant构造函数构造临时explicit对象来调用隐式定义的复制赋值运算符:

template<typename T> variant(T value)

如果删除复制构造函数或将构造函数标记为显式,则可以看到这种情况发生:

variant(const variant&) = delete;
template<typename T> explicit variant(T value)
海湾合作委员会说:

test.cpp:100:14: error: invalid initialization of non-const reference of type ‘std::__cxx11::basic_string<char>&’ from an rvalue of type ‘std::__cxx11::string {aka std::__cxx11::basic_string<char>}’
   var_test_2 = std::string("Hello again !");

如果您更改赋值运算符以使用const引用,那么您的代码将按预期工作。我还建议将你的构造函数标记为explicit,否则会遇到像这样的非常微妙的问题。

Live demo

小一点:__get_storage是一个保留名称,因为它以两个下划线开头。你不应该在你的代码中使用它。