I am using a custom allocator which needs to know the real type of an object upon deletion. *It should also deallocate the same exact address as was received from the allocate function. This is my first project using any kind of special allocators so I really have no experience or knowledge of best practices. I would like to essentially have the same semantics as a unique pointer. Here an obvious failed attempt to show the problem:

template<typename T>
void* allocate(){
    return malloc(sizeof(T)); //I really do something else but its beyond the scope of this example
}

template<typename T>
void deallocate(T * p){
    // T must be real T and not base of T
    free(p); //I really do something else but its beyond the scope of this example
}
std::unique_ptr<Base> pb(new Base);
std::unique_ptr<Derived> pd(new Derived);
std::unique_ptr<Base> pb2(std::move(pd));
//std::unique_ptr<Derived> pd2(std::move(pb)); this should not compile
//pb.swap(pd); this should not compile
pb.swap(pb2);

This fails because unique_ptr needs to call my deallocate rather than delete.

What is the best pointer to use in this case?

UPDATE:

adding a stateful costume deleter with type erasure would probably solve my problem. Here is an example with a std::function

template<typename T>
using UniquePtr = std::unique_ptr<T,std::function<void(T*)>>;

//using my special unique_ptr
UniquePtr<Base> upb(new (allocate<Derived>()) Derived,[](Base*p){
        if(p!=nullptr){
            auto d = static_cast<Derived*>(p);
            d->~Derived();
            deallocate(d); 
        }
    });

I can't think of another way to let the knowledge of the real derived class follow the pointer in the case that it is converted to a base and then moved around. This seems very verbose and error prone though. Is this the best way ?

EDIT 2: adding a factory function takes care of some of the ugliness and makes this less error prone:

template<typename T_Base, typename T_Derived, typename... Ts>
std::unique_ptr<T_Base,std::function<void(T_Base* p)>> MakeUnique(Ts &&... Args){
    return std::unique_ptr<T_Base,std::function<void(T_Base*)>>(
        new (allocate<T_Derived>()) T_Derived(std::forward<Ts>(Args)...),
        [](T_Base*p){
            if(p!=nullptr){
                auto d = static_cast<T_Derived*>(p);
                d->~T_Derived();
                deallocate(d); 
            }
        });
}

//example use
auto mupd = MakeUnique<Base,Derived>();
auto mupdd = MakeUnique<Derived,Derived>();
auto mupb = MakeUnique<Base,Derived>();
auto mupbb = MakeUnique<Base,Base>();
mupbb.swap(mupb);
mupb = std::move(mupd);
//mupb = std::move(mupdd); should give error

This is starting to feel usable so it seems I may have solved my own problem. I am still quite greatfull to anyone offering critique or thoughts.

*I was originally unclear about this requirement.

有帮助吗?

解决方案

Not sure if this is what you're looking for. But you can get a lot of the functionality of a "dynamic deleter" by using a function pointer as a deleter with unique_ptr:

std::unique_ptr<Base, void(*)(void*)> p(allocate<Derived>(), deallocate<Derived>);

The deleter is just a function. But at run time you can feed it a pointer to a templated function, templated on your Derived type. It allows the right destructor to be called, even if the destructor isn't virtual. Though anything else called via this pointer must be virtual, or it will resolve to the Base class of course. Here's a full HelloWorld:

#include <memory>
#include <new>
#include <cstdlib>

template<typename T>
T*
allocate()
{
    std::unique_ptr<T, void(*)(void*)> hold(static_cast<T*>(std::malloc(sizeof(T))),
                                                            std::free);
    ::new (hold.get()) T;
    return static_cast<T*>(hold.release());
}

template<typename T>
void
deallocate(void* p)
{
    static_cast<T*>(p)->~T();
    std::free(p);
}

#include <iostream>

struct Base
{
    Base() {std::cout << "Base()\n";}
    Base(const Base&) = delete;
    Base& operator=(const Base&) = delete;
    ~Base() {std::cout << "~Base()\n";}

    virtual void bark() const {std::cout << "Hi Base!\n";}
};

struct Derived
    : public Base
{
    Derived() {std::cout << "Derived()\n";}
    Derived(const Base&) = delete;
    Derived& operator=(const Derived&) = delete;
    ~Derived() {std::cout << "~Derived()\n";}

    void bark() const {std::cout << "Hi Derived!\n";}
};

int
main()
{
    std::unique_ptr<Derived, void(*)(void*)> p(allocate<Derived>(), deallocate<Derived>);
    p->bark();
    std::unique_ptr<Base, void(*)(void*)> p2 = std::move(p);
    p2->bark();
}

Base()
Derived()
Hi Derived!
Hi Derived!
~Derived()
~Base()
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top