C++11 memory pool design pattern?

前端 未结 5 394
鱼传尺愫
鱼传尺愫 2020-12-07 13:34

I have a program that contains a processing phase that needs to use a bunch of different object instances (all allocated on the heap) from a tree of polymorphic types, all e

5条回答
  •  有刺的猬
    2020-12-07 14:15

    Your idea is great and millions of applications are already using it. This pattern is most famously known as «autorelease pool». It forms a base for ”smart” memory management in Cocoa and Cocoa Touch Objective-C frameworks. Despite the fact that C++ provides hell of a lot of other alternatives, I still think this idea got a lot of upside. But there are few things where I think your implementation as it stands may fall short.

    The first problem that I can think of is thread safety. For example, what happens when objects of the same base are created from different threads? A solution might be to protect the pool access with mutually exclusive locks. Though I think a better way to do this is to make that pool a thread-specific object.

    The second problem is invoking an undefined behavior in case where derived class's constructor throws an exception. You see, if that happens, the derived object won't be constructed, but your B's constructor would have already pushed a pointer to this to the vector. Later on, when the vector is cleared, it would try to call a destructor through a virtual table of the object that either doesn't exist or is in fact a different object (because new could reuse that address).

    The third thing I don't like is that you have only one global pool, even if it is thread-specific, that just doesn't allow for a more fine grained control over the scope of allocated objects.

    Taking the above into account, I would do a couple of improvements:

    1. Have a stack of pools for more fine-grained scope control.
    2. Make that pool stack a thread-specific object.
    3. In case of failures (like exception in derived class constructor), make sure the pool doesn't hold a dangling pointer.

    Here is my literally 5 minutes solution, don't judge for quick and dirty:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define thread_local __thread // Sorry, my compiler doesn't C++11 thread locals
    
    struct AutoReleaseObject {
        AutoReleaseObject();
        virtual ~AutoReleaseObject();
    };
    
    class AutoReleasePool final {
      public:
        AutoReleasePool() {
            stack_.emplace(this);
        }
    
        ~AutoReleasePool() noexcept {
            std::set obj;
            obj.swap(objects_);
            for (auto *p : obj) {
                delete p;
            }
            stack_.pop();
        }
    
        static AutoReleasePool &instance() {
            assert(!stack_.empty());
            return *stack_.top();
        }
    
        void add(AutoReleaseObject *obj) {
            objects_.insert(obj);
        }
    
        void del(AutoReleaseObject *obj) {
            objects_.erase(obj);
        }
    
        AutoReleasePool(const AutoReleasePool &) = delete;
        AutoReleasePool &operator = (const AutoReleasePool &) = delete;
    
      private:
        // Hopefully, making this private won't allow users to create pool
        // not on stack that easily... But it won't make it impossible of course.
        void *operator new(size_t size) {
            return ::operator new(size);
        }
    
        std::set objects_;
    
        struct PrivateTraits {};
    
        AutoReleasePool(const PrivateTraits &) {
        }
    
        struct Stack final : std::stack {
            Stack() {
                std::unique_ptr pool
                    (new AutoReleasePool(PrivateTraits()));
                push(pool.get());
                pool.release();
            }
    
            ~Stack() {
                assert(!stack_.empty());
                delete stack_.top();
            }
        };
    
        static thread_local Stack stack_;
    };
    
    thread_local AutoReleasePool::Stack AutoReleasePool::stack_;
    
    AutoReleaseObject::AutoReleaseObject()
    {
        AutoReleasePool::instance().add(this);
    }
    
    AutoReleaseObject::~AutoReleaseObject()
    {
        AutoReleasePool::instance().del(this);
    }
    
    // Some usage example...
    
    struct MyObj : AutoReleaseObject {
        MyObj() {
            std::cout << "MyObj::MyObj(" << this << ")" << std::endl;
        }
    
        ~MyObj() override {
            std::cout << "MyObj::~MyObj(" << this << ")" << std::endl;
        }
    
        void bar() {
            std::cout << "MyObj::bar(" << this << ")" << std::endl;
        }
    };
    
    struct MyObjBad final : AutoReleaseObject {
        MyObjBad() {
            throw std::runtime_error("oops!");
        }
    
        ~MyObjBad() override {
        }
    };
    
    void bar()
    {
        AutoReleasePool local_scope;
        for (int i = 0; i < 3; ++i) {
            auto o = new MyObj();
            o->bar();
        }
    }
    
    void foo()
    {
        for (int i = 0; i < 2; ++i) {
            auto o = new MyObj();
            bar();
            o->bar();
        }
    }
    
    int main()
    {
        std::cout << "main start..." << std::endl;
        foo();
        std::cout << "main end..." << std::endl;
    }
    

提交回复
热议问题