c++ - C++11 memory pool design pattern? -


i have program contains processing phase needs use bunch of different object instances (all allocated on heap) tree of polymorphic types, derived common base class.

as instances may cyclically reference each other, , not have clear owner, want allocated them new, handle them raw pointers, , leave them in memory phase (even if become unreferenced), , after phase of program uses these instances, want delete them @ once.

how thought structure follows:

struct b; // common base class  vector<unique_ptr<b>> memory_pool;  struct b {     b() { memory_pool.emplace_back(this); }      virtual ~b() {} };  struct d : b { ... }  int main() {     ...      // phase begins     d* p = new d(...);      ...      // phase ends     memory_pool.clear();     // b instances deleted, , pointers invalidated      ... } 

apart being careful b instances allocated new, , noone uses pointers them after memory pool cleared, there problems implementation?

specifically concerned fact this pointer used construct std::unique_ptr in base class constructor, before derived class constructor has completed. result in undefined behaviour? if there workaround?

your idea great , millions of applications using it. pattern famously known «autorelease pool». forms base ”smart” memory management in cocoa , cocoa touch objective-c frameworks. despite fact c++ provides hell of lot of other alternatives, still think idea got lot of upside. there few things think implementation stands may fall short.

the first problem can think of thread safety. example, happens when objects of same base created different threads? solution might protect pool access mutually exclusive locks. though think better way make pool thread-specific object.

the second problem invoking undefined behavior in case derived class's constructor throws exception. see, if happens, derived object won't constructed, b's constructor have pushed pointer this vector. later on, when vector cleared, try call destructor through virtual table of object either doesn't exist or in fact different object (because new reuse address).

the third thing don't have 1 global pool, if thread-specific, doesn't allow more fine grained control on scope of allocated objects.

taking above account, couple of improvements:

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

here literally 5 minutes solution, don't judge quick , dirty:

#include <new> #include <set> #include <stack> #include <cassert> #include <memory> #include <stdexcept> #include <iostream>  #define thread_local __thread // sorry, compiler doesn't c++11 thread locals  struct autoreleaseobject {     autoreleaseobject();     virtual ~autoreleaseobject(); };  class autoreleasepool final {   public:     autoreleasepool() {         stack_.emplace(this);     }      ~autoreleasepool() noexcept {         std::set<autoreleaseobject *> obj;         obj.swap(objects_);         (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 private won't allow users create pool     // not on stack easily... won't make impossible of course.     void *operator new(size_t size) {         return ::operator new(size);     }      std::set<autoreleaseobject *> objects_;      struct privatetraits {};      autoreleasepool(const privatetraits &) {     }      struct stack final : std::stack<autoreleasepool *> {         stack() {             std::unique_ptr<autoreleasepool> 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); }  // usage example...  struct myobj : autoreleaseobject {     myobj() {         std::cout << "myobj::myobj(" << << ")" << std::endl;     }      ~myobj() override {         std::cout << "myobj::~myobj(" << << ")" << std::endl;     }      void bar() {         std::cout << "myobj::bar(" << << ")" << std::endl;     } };  struct myobjbad final : autoreleaseobject {     myobjbad() {         throw std::runtime_error("oops!");     }      ~myobjbad() override {     } };  void bar() {     autoreleasepool local_scope;     (int = 0; < 3; ++i) {         auto o = new myobj();         o->bar();     } }  void foo() {     (int = 0; < 2; ++i) {         auto o = new myobj();         bar();         o->bar();     } }  int main() {     std::cout << "main start..." << std::endl;     foo();     std::cout << "main end..." << std::endl; } 

Comments

Popular posts from this blog

Perl - how to grep a block of text from a file -

delphi - How to remove all the grips on a coolbar if I have several coolbands? -

javascript - Animating array of divs; only the final element is modified -