Run-Time Check Failure #2



  • @Swordfish sagte in Run-Time Check Failure #2:

    Der Compiler dürfte ohne launder() davon ausgehen daß sich die Elemente hinter dem T const* nicht verändert haben.

    Nein. Er darf nur davon ausgehen dass es immer noch das selbe Element ist. Aber da das Element selbst nicht const ist, darf er nicht davon ausgehen dass es sich nicht geändert haben kann.

    Ein Problem ergibt sich nur dann wenn Objekte erzeugt und zerstört und wieder erzeugt werden -- an der selben Adresse. Und genau da geht mir jetzt auch gerade ein Licht auf, denn genau das kann beim vector ja passieren.

    Also das Problem wäre nur hier:

    vector<T> vec;
    vec.push_back(1);
    int a = vec[0].constMember;
    vec.pop_back();
    vec.push_back(2);
    int b = vec[0].constMember;
    

    Hier ist vec[0] nicht mehr das selbe Ding wie vorher. Es hat den selben Typ und steht an der selben Stelle, aber es ist ein neues Objekt. Daher dürfen sich const Member geändert haben. Und genau das, also dass hier etwas zerstört und ein neues Etwas an der selben Stelle wieder erzeugt wurde muss man daher dem Compiler mitteilen.



  • @Swordfish sagte in Run-Time Check Failure #2:

    Der Compiler dürfte ohne launder() davon ausgehen daß sich die Elemente hinter dem T const* nicht verändert haben.

    Davon steht hier nichts. Allerdings ist es in dem hier verwendeten Kontext laut dieser Referenz sinnvoll, weil so man anzeigen kann, dass über diesen Zeiger nicht mehr auf nicht initialisierten Speicher zugreift, sondern die Objekte mit placement new konstruiert worden sind.



  • So kann man das ganz ohne Casts nutzen, wenn man schon Delegation nutzt.

    #include <memory>
    #include <cstddef>
    #include <iterator>
    
    template <typename T, class Allocator = std::allocator<T>>
    class SimpleStorage {
    public:
            using allocator_type    = Allocator;
            using alloc_traits              = typename std::allocator_traits<allocator_type>;
            using size_type                 = typename alloc_traits::size_type;
    
            allocator_type  alloc;
            size_type       c;
            T* p;
    
            ~SimpleStorage () {alloc.deallocate(p,c);}
            SimpleStorage (const size_type capacity, allocator_type& a) : c(capacity), p(alloc.allocate(c)) {}
            SimpleStorage (const size_type capacity) : alloc({}), c(capacity), p(alloc.allocate(c)) {}
            SimpleStorage (const SimpleStorage&) = delete;
            SimpleStorage (SimpleStorage&&) = delete;
            SimpleStorage& operator= (const SimpleStorage&) = delete;
            SimpleStorage& operator= (SimpleStorage&&) = delete;
    };
    
    template<typename T>
    class vector_t {
    public:
            using value_type       = T;
            using pointer          = T*;
            using const_pointer    = const T*;
            using reference        = T&;
            using const_reference  = T const&;
            using size_type        = std::size_t;
    private:
            size_type  data_size;
            std::unique_ptr<SimpleStorage<T>> storage;
    public:
        pointer data() noexcept {return std::launder(storage->p);}
        const_pointer data() const noexcept {return std::launder(storage->p);}
    
            explicit vector_t(std::size_t size = 0, value_type const &value = value_type{})
                    : data_size(size), storage(std::make_unique<SimpleStorage<T>>(data_size))
            {
                std::uninitialized_fill_n(data(), data_size, value);
            }
    
            vector_t(vector_t<T> const &other) : vector_t(other.begin(), other.end()) {}
    
            vector_t(std::initializer_list<value_type> ilist) : vector_t(ilist.begin(), ilist.end()) {}
    
        template <typename ForwardIt>
            vector_t(ForwardIt first, ForwardIt last)
                    : data_size(std::distance(first, last)), storage(std::make_unique<SimpleStorage<T>>(data_size))
            {
                    std::uninitialized_copy_n(first, data_size, storage->p);
            }
    
            friend void swap(vector_t<T> &a, vector_t<T> &b) noexcept {
                    using std::swap;
                    swap(a.data_size, b.data_size);
                    swap(a.storage, b.storage);
            }
    
            vector_t& operator=(vector_t<T> other) noexcept {
                    swap(*this, other);
                    return *this;
            }
    
            ~vector_t() {
                std::destroy_n(data(), size());
            }
    
              pointer begin()       noexcept {return data();}
              pointer end()         noexcept {return data() + size();}
            const_pointer begin() const noexcept {return data();}
            const_pointer end()   const noexcept {return data() + size();}
    
            size_type size()     const noexcept { return data_size; }
            size_type capacity() const noexcept { return storage->c; }
    
                  reference operator[](size_type index)       { return data()[index]; }
            const_reference operator[](size_type index) const { return data()[index]; }
    
    private:
            reference checked_access(size_type index) {
                    if (data_size <= index)
                            throw std::out_of_range{ "vector_t::at(" + std::to_string(index) + ")" };
                    return operator[](index);
            }
    
    public:
                  reference at(size_type index)       { return checked_access(index); }
            const_reference at(size_type index) const { return const_cast<vector_t*>(this)->checked_access(index); }
    };
    
    #include <string>
    #include <iostream>
    int main() {
        vector_t<std::string> vec{"asdf", "Swordfish", "blub"};
        vector_t vec2 = vec;
        for (auto& s : vec2) std::cout << s << std::endl;
    
    }
    
    

Anmelden zum Antworten