using std::unique_ptr with allocators
Asked Answered
D

1

13

I was trying my hand with allocators this time & felt that there were many chances of leaking the resources. So I thought what if I used std::unique_ptr to handle them. I tried my hand with a std::vector's allocator. My code goes like this :-

// allocator
#include <iostream>
#include <vector>
#include <memory>
using namespace std;

class X
{
    int x,ID;
    static int i;
    public:
        X()
        {
            cout<<"constructing ";
            ID=++i;
            cout<<"ID="<<ID<<'\n';
        }
        X(int a)
        {
            x=a;
            cout<<"constructing ";
            ID=++i;
            cout<<"ID="<<ID<<'\n';
        }
        void get()
        {
            cout<<"enter x: ";
            cin>>x;
        }
        void disp()
        {
            cout<<"x="<<x<<'\t';
        }
        ~X()
        {
            cout<<"destroying ID="<<ID<<'\n';
        }
};
int X:: i=0;

int main()
{
    ios::sync_with_stdio(false);
    vector<X> v;
    auto alloc = v.get_allocator();
    unsigned int i=0;

    X *p(alloc.allocate(5));        
    for (i=0;i<5;++i)
    alloc.construct (&p[i], i+1);
    unique_ptr<X[]> ptr(p);

    cout<<"\nthe elements are:-\n";
    for (i=0;i<5;++i)
    {
        ptr[i].disp();
        cout << '\t' << (long long)alloc.address(ptr[i]) << '\n';
    }
    cout<<"\n";

    /*for (i=0;i<5;++i)
    alloc.destroy(&p[i]);
    deallocate(p,16)*/

    return 0;
}

Unfortunately this code crashes showing UB. So what should I do ? How should I manipulate my code so as to make it suited for std::unique_ptr ?

Damn answered 21/11, 2015 at 15:29 Comment(8)
i guess u wud need to write a separate make_unique type function & custom deleter in this caseSchematic
@CppNITR could you explain it with a code ?Damn
See here.Barrington
Please format your code properly.Mackey
@black what do you want me to doDamn
@KerrekSB couldn't it be simplerDamn
Hey, you're the one who started it by saying the "a"-word. You got what you deserve :-)Barrington
@KerrekSB :P still....Damn
Z
11
template<typename T>
std::unique_ptr<T[], std::function<void(T *)>> make_T(X *ptr, std::allocator<T> alloc, std::size_t size) {
    auto deleter = [](T *p, std::allocator<T> alloc, std::size_t size) {
        for (int i = 0; i < size; ++i) {
            alloc.destroy(&p[i]);
        }
        alloc.deallocate(p, sizeof(T) * size);
    };

    return {ptr, std::bind(deleter, std::placeholders::_1, alloc, size)};
}



int main(int argc, const char * argv[]) {
    std::allocator<X> alloc = std::allocator<X>();

    X *p = alloc.allocate(5);
    for (int i = 0; i < 5; ++i) {
        alloc.construct(&p[i], i + 1);
    }

    auto ptr = make_T(p, alloc, 5);

    return 0;
}

Can also write one to construct the objects for you:

template<typename T, typename... Args>
std::unique_ptr<T[], std::function<void(T *)>> make_T_Construct(std::allocator<T> alloc, std::size_t size, Args... args) {

    X *ptr = alloc.allocate(size);

    for (std::size_t i = 0; i < size; ++i) {
        alloc.construct(&ptr[i], std::forward<Args>(args)...);
    }


    auto deleter = [](T *p, std::allocator<T> alloc, std::size_t size) {
        for (std::size_t i = 0; i < size; ++i) {
            alloc.destroy(&p[i]);
        }
        alloc.deallocate(p, sizeof(T) * size);
    };

    return {ptr, std::bind(deleter, std::placeholders::_1, alloc, size)};
}

int main(int argc, const char * argv[]) {
    std::allocator<X> alloc = std::allocator<X>();

    auto ptr = make_T_Construct(alloc, 5, 100);

    return 0;
}

Edit: To do what you want (tracking allocations), you have to track the memory allocations yourself using a custom allocator..

template<typename T>
struct Allocator
{
    typedef T value_type;

    Allocator() noexcept {};

    template<typename U>
    Allocator(const Allocator<U>& other) throw() {};

    T* allocate(std::size_t n, const void* hint = 0)
    {
        T* memory = static_cast<T*>(::operator new(n * (sizeof(T) + sizeof(bool))));

        for (std::size_t i = 0; i < n * (sizeof(T) + sizeof(bool)); ++i)
        {
            *reinterpret_cast<bool*>(reinterpret_cast<char*>(memory) + sizeof(bool)) = false;
        }

        return memory;
    }

    void deallocate(T* ptr, std::size_t n)
    {
        ::operator delete(ptr);
    }

    void construct(T* p, const T& arg)
    {
        destroy(p);
        new(p) T(arg);
        *reinterpret_cast<bool*>(reinterpret_cast<char*>(p) + sizeof(bool)) = true;
    }

    template<class U, class... Args>
    void construct(U* p, Args&&... args)
    {
        destroy(p);
        ::new(p) U(std::forward<Args>(args)...);

        *reinterpret_cast<bool*>(reinterpret_cast<char*>(p) + sizeof(bool)) = true;
    }

    void destroy(T* p)
    {
        if (*reinterpret_cast<bool*>(reinterpret_cast<char*>(p) + sizeof(bool))) {
            p->~T();
            *reinterpret_cast<bool*>(reinterpret_cast<char*>(p) + sizeof(bool)) = false;
        }
    }

    template<class U>
    void destroy(U* p)
    {
        if (*reinterpret_cast<bool*>(reinterpret_cast<char*>(p) + sizeof(bool))) {
            p->~U();
            *reinterpret_cast<bool*>(reinterpret_cast<char*>(p) + sizeof(bool)) = false;
        }
    }
};

template <typename T, typename U>
inline bool operator == (const Allocator<T>&, const Allocator<U>&)
{
    return true;
}

template <typename T, typename U>
inline bool operator != (const Allocator<T>& a, const Allocator<U>& b)
{
    return !(a == b);
}
Zerla answered 21/11, 2015 at 16:32 Comment(7)
i appreciate ur efforts but this can't prevent memory leaksDamn
@AnkitAcharya it would be better if you show your code which leakedBotticelli
cpp.sh/5wp6b check this. ID = 6 will leak here. I know it leaks because of the for loop statement but my point is that unique_ptr should take care of such erogenous construction as it always doesDamn
Wait.. so you're allocating a class into some memory. Overwriting the memory and expecting the class to automatically destruct.. I don't know if you understand how allocators work, but the class will NEVER destruct until you call destroy. That's not std::unique_ptr fault. It's your fault for intentionally leaking it. Allocators use placement new and you must delete it by calling the destructor explicitly (which is what the point of destroy is). Your problems lie elsewhere.Zerla
got it @Brandon, then it means unique_ptr is apparently useless when it comes to allocators or is it ?Damn
@AnkitAcharya it isn't.. cpp.sh/5c6u Just use a custom allocator that tracks memory allocations.. std::unique_ptr is doing it's job correctly. It's destroying your array, not the allocated objects within that array. It does NOT know that you allocated those objects using placement new. However, if you created an allocator that KNOWS this information, then std::unique_ptr will do what you want (it's actually the allocator that is doing the work since it does the allocations & destructions).Zerla
@Zerla i guess you should prefer using over typedef & noexcept over throw()Schematic

© 2022 - 2024 — McMap. All rights reserved.