2016-11-04 43 views
1

我想重寫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,但我不知道在哪裏以及如何。

我得到的錯誤:

Exception thrown at 0x00096370 in Test.exe: 0xC0000005: Access violation writing location 0xDDDDDDDD.

更新:似乎調用析構函數兩次,但我沒有找到原因。

+0

你的「破壞問題」是什麼? – Carcigenicate

+0

@Carcigenicate更新 – Boiethios

+1

>我想重寫std :: variant的基礎,因爲我必須使用C++ 14編譯器: 使用boost :: variant? –

回答

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) 

GCC說:

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是保留名稱,因爲它有兩個下劃線開始。你不應該在你的代碼中使用它。

+0

非常感謝您的回答和你最後的建議。我開始瞭解C++,但仍然有很多我遇到的麻煩...... – Boiethios

+1

這個答案需要提及五/零規則。 – Yakk

+0

@Yakk是的,也許我需要手動執行復制/移動構造函數。 – Boiethios