2016-11-04 3 views
1

Ich versuche, die Basis von std::variant umschreiben, weil ich einen C++ 14 Compiler (ohne Std :: Variante) verwenden muss und ich brauche eine typsichere enum.Destruction Fehler beim Implementieren Variante

Mein Code funktioniert nur mit Grundtypen (dh int, float, char *, etc.) Aber wenn std::string zum Beispiel mit, ich habe Probleme mit der Zerstörung.

Hier ist mein Code:

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 
} 

Ich habe ein vollständiges Codebeispiel, sondern nur die Umsetzung von variant ist wichtig.

Ich nehme an, ich habe die std::aligned_union schlecht verwendet, aber ich kann nicht herausfinden, wo und wie.

ich den Fehler:

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

Update: es scheint der destructor zweimal genannt, aber ich finde nicht, warum.

+0

Was sind Ihre Probleme mit der Zerstörung? – Carcigenicate

+0

@Carcigenicate aktualisiert – Boiethios

+1

> Ich versuche, die Basis von Std :: Variante neu schreiben, weil ich einen C++ 14 Compiler verwenden muss: Verwenden Sie boost :: variant? –

Antwort

3

Ihr Zuweisungsoperator ist falsch:

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

Dies nimmt nur lvalues, aber Sie versuchen, einen R-Wert zuweisen:

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

Statt einen eigenen Zuweisungsoperator des Aufrufs, ruft er die implizit definiert kopieren Sie den Zuweisungsoperator, indem Sie ein temporäres variant Objekt über Ihren Nicht-explicit Konstruktor erstellen:

können Sie dies sehen, ob Sie die Copykonstruktor löschen oder Ihr Konstruktor markieren als explizite:

variant(const variant&) = delete; 
template<typename T> explicit variant(T value) 

GCC sagt:

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 !"); 

Wenn Sie Ihren Zuweisungsoperator ändern dann const Referenzen verwenden, um Ihre Code funktioniert wie gewünscht. Ich würde außerdem vorschlagen, Ihren Konstruktor als explicit zu markieren, sonst stößt man auf wirklich subtile Probleme wie diese.

Live demo


Eine kleine beiseite: __get_storage ist ein reservierter Name, da es mit zwei Unterstrichen beginnt. Sie sollten es nicht in Ihrem Code verwenden.

+0

Vielen Dank für Ihre Antwort und Ihren letzten Rat. Ich fange an, C++ zu kennen, aber es gibt immer noch eine Menge Feinheiten, mit denen ich Probleme habe ... – Boiethios

+1

Diese Antwort muss die Regel five/zero erwähnen. – Yakk

+0

@Yakk Ja, vielleicht muss ich die Copy/Move-Konstruktoren von Hand implementieren. – Boiethios

Verwandte Themen