我正在尝试重写std::variant
的基础,因为我必须使用c ++ 14编译器(没有std :: variant),我需要一个类型安全的枚举。
我的代码仅适用于基本类型(例如int
,float
,char *
等。)但是当使用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。
更新:似乎析构函数被调用两次,但我找不到原因。
答案 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
,否则会遇到像这样的非常微妙的问题。
小一点:__get_storage
是一个保留名称,因为它以两个下划线开头。你不应该在你的代码中使用它。