mardi 27 mars 2018

Why is my C++11 allocator's dtor called twice?

I wrote my own basic c++11 allocator to break it down and see how it works. Things go smoothly for the most part but for some reason my dtor is called twice. This isn't a problem right now but I can imagine a scenario where a stateful allocator attempts to dereference nullptr the second time the dtor is called, so I'm curious if this is expected behavior, and if it is, is there a way to avoid it?

Here's the code:

#include<iostream>
#include<vector>
#include<new>

using std::vector;
using std::cout;
using std::endl;
using __gnu_cxx::ptrdiff_t;

template<typename elem_t>
struct myAlloc {
    typedef elem_t value_type;
    typedef elem_t * pointer;
    typedef const elem_t * const_pointer;
    typedef elem_t & reference;
    typedef const elem_t & const_reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    template<typename other_elem_t>
    struct rebind {
        typedef myAlloc<other_elem_t> other;
    };

    myAlloc() {
        cout << "constructing myAlloc" << endl;
    }
    ~myAlloc() {
        cout << "destructing myAlloc" << endl;
    }
    pointer address(reference x) const {
        cout << "myAlloc retrieving address" << endl;
        return &x;
    }
    const_pointer address(const_reference x) const {
        cout << "myAlloc retrieving address of x" << endl;
        return &x;
    }
    pointer allocate(size_type n, const void * hint = 0) {
        cout << "myAlloc allocating n elem_t's" << endl;
        pointer retval = (pointer)malloc(n * sizeof(value_type));

        if (retval == nullptr)
            throw std::bad_alloc();
        return retval;
    }
    void deallocate(pointer p, size_type n) {
        cout << "myAlloc deallocating n elem_t's" << endl;
        free(p);
    }
    size_type max_size() const throw() {
        return -1;
    }
    void construct(pointer p, const_reference val) {
        cout << "Constructing an elem_t 'val' at address p" << endl;
        new ((void*)p) value_type (val);
    }
    void destroy(pointer p) {
        cout << "Destroying the elem_t at address p" << endl;
        p->~value_type();
    }
};  

void test() {
    cout << "creating vec" << endl;
    vector<int, myAlloc<int> > myvec = {1, 2, 3, 4, 5};
    cout << "done creating vec" << endl;
}   
int main() {
    test();
    cout << "main" << endl;
}

And here's the output:

creating vec
constructing myAlloc
myAlloc allocating n elem_t's
Constructing an elem_t 'val' at address p
Constructing an elem_t 'val' at address p
Constructing an elem_t 'val' at address p
Constructing an elem_t 'val' at address p
Constructing an elem_t 'val' at address p
destructing myAlloc
done creating vec
Destroying the elem_t at address p
Destroying the elem_t at address p
Destroying the elem_t at address p
Destroying the elem_t at address p
Destroying the elem_t at address p
myAlloc deallocating n elem_t's
destructing myAlloc  
main

My guess is that the allocator is constructed locally whenever it's use and destructed as soon as it's done allocating the object. But then we call the destroy and deallocate functions despite the allocator being destructed. Why is it implemented this way? How it useful to destruct the allocator as soon as allocation's done? And how does calling a member function after deallocation not break things in the general case?

Aucun commentaire:

Enregistrer un commentaire