Patrick Wright
Patrick Wright

Reputation: 1663

Returning std::unique_ptr to abstract type with custom deleter from a memory pool

Assume I have a templated MemoryPool class a function create(...) (which returns a pointer to a newly allocated object of type T) and a function destroy(T*) (which destroys and returns the memory back to the pool).

I would like to create a std::unique_ptr that "owns" the pointer created by the pool and returns the pointer to the pool, thus requiring a custom deleter.

The problem is, how do I make this work if the pool contains concrete objects and I want to pass around a std::unique_ptr to an abstract interface of this object.

Here is an example that doesn't compile:

#include <iostream>
#include <memory>
#include <functional>
#include <utility>

template <typename T>
class MemoryPool {
public:
    template <typename ... ARGS>
    T* create(ARGS&&... args) {
        std::cout << "MemoryPool::create()" << std::endl;
        
        return new T(std::forward<ARGS>(args)...);
    }
    
    void destroy(T* ptr) {
        std::cout << "MemoryPool::destroy()" << std::endl;
        
        delete ptr;
    }
};

class ITest {
public:
    ITest() {
        std::cout << "ITest::ITest()" << std::endl;
    }
    virtual ~ITest() {
        std::cout << "ITest::~ITest()" << std::endl;
    }
    
    virtual void sayHello() = 0;
};

class Test :public ITest {
public:
    Test() {
        std::cout << "Test::Test()" << std::endl;
    }
    ~Test() {
        std::cout << "Test::~Test()" << std::endl;
    }
    
    void sayHello() override { 
        std::cout << "Test says hello" << std::endl; 
    }
};

class ITestOwner {
public:
    ITestOwner(std::unique_ptr<ITest> ptr) : 
        _ptr(std::move(ptr)) 
    {
        std::cout << "ITestOwner::ITestOwner()" << std::endl;
    }
    
    ~ITestOwner() {
        std::cout << "ITestOwner::~ITestOwner()" << std::endl;
    }
    
    void sayHello() { _ptr->sayHello(); }
    
private:
    std::unique_ptr<ITest> _ptr;
};

int main() {
    MemoryPool<Test> pool;
    
    std::unique_ptr<Test, std::function<void(Test*)>> ptr(pool.create(), [&pool](Test* ptr){
        std::cout << "Custom Deleter" << std::endl;
        pool.destroy(ptr);
    });
    
    ITestOwner owner(std::move(ptr));
    
    owner.sayHello();
    
    return 0;
}

Keep in mind that, my real MemoryPool class would actually act as a normal memory pool and not use new/delete as I have done.

In this example, ITestOwner should take over ownership of the std::unique_ptr to a ITest abstract object. Then, when ITestOwner is destroyed, the smart pointer will be destroyed, and the Test object should be returned to the memory pool.

Is there a way to accomplish this?

Upvotes: 3

Views: 416

Answers (1)

ALX23z
ALX23z

Reputation: 4713

The code doesn't compile because std::unique_ptr<ITest> is used by ITestOwner while you to forward it std::unique_ptr<Test, std::function<void(Test*)>>. It obviously doesn't compile because std::unique_ptr<ITest> calls delete on ITest instead of calling some complex arbitrary function.

You'd need to use unique_ptr<ITest, function<void(ITest*)>> for it to work and in addition add some unsighty conversion code from function<void(Test*)> to function<void(ITest*)>... I'd say this is simply not good. unique_ptr is designed to be simple and efficient - the destructor is supposed to wrap basic functionality but it isn't convenient enough for complicated purposes.

Basically, unique_ptr is not designed for this task. It is supposed to be lightweight and you already use heavy functionality like std::function that ruins the whole purpose. Instead, you can use shared_ptr which type erases the deleter and hides it - so you'd need nothing to worry about. If you want to still restrict user to unique ownership you can surely find other 3rd party open source libraries that implement the smart pointer you want - there are lots of them.

Upvotes: -1

Related Questions