1

I have a scenario where I have multiple operations represented in the following way:

struct Op {
  virtual void Run() = 0;
};

struct FooOp : public Op {
  const std::vector<char> v;
  const std::string s;
  FooOp(const std::vector<char> &v, const std::string &s) : v(v), s(s) {}
  void Run() { std::cout << "FooOp::Run" << '\n'; }
};

// (...)

My application works in several passes. In each pass, I want to create many of these operations and at the end of the pass I can discard them all at the same time. So I would like to preallocate some chunk of memory for these operations and allocate new operations from this memory. I came up with the following code:

class FooPool {
public:
  FooPool(int size) {
    foo_pool = new char[size * sizeof(FooOp)]; // what about FooOp alignment?
    cur = 0;
  }

  ~FooPool() { delete foo_pool; }

  FooOp *New(const std::vector<char> &v, const std::string &s) {
    return new (reinterpret_cast<FooOp*>(foo_pool) + cur) FooOp(v,s);
  }

  void Release() {
    for (int i = 0; i < cur; ++i) {
      (reinterpret_cast<FooOp*>(foo_pool)+i)->~FooOp();
    }
    cur = 0;
  }

private:
  char *foo_pool;
  int cur;
};

This seems to work, but I'm pretty sure I need to take care somehow of the alignment of FooOp. Moreover, I'm not even sure this approach is viable since the operations are not PODs.

  • Is my approach flawed? (most likely)
  • What's a better way of doing this?
  • Is there a way to reclaim the existing memory using unique_ptrs?

Thanks!

ale64bit
  • 6,232
  • 3
  • 24
  • 44
  • 3
    Look into [placement new](https://stackoverflow.com/questions/222557/what-uses-are-there-for-placement-new) and [`std::aligned_storage`](https://en.cppreference.com/w/cpp/types/aligned_storage). – Cornstalks Jul 06 '18 at 02:40
  • 1
    You should manually call the destructor when you use placement new. Yes, and as @vu1p3n0x pointed out, you can't destroy that memory as you didn't allocate just that chunk, you'll need to delete the whole block you're using for the pool when you're done with it. – Retired Ninja Jul 06 '18 at 02:40
  • 3
    don't use `delete` with `placement new` – kmdreko Jul 06 '18 at 02:41
  • @Cornstalks I looked into `std::aligned_storage` but unfortunately it's c++17 only. About placement new, I thought I was doing precisely that in my code, i.e. `new (addr) Ctor(...)`. – ale64bit Jul 06 '18 at 02:42
  • https://stackoverflow.com/questions/1022320/why-destructor-is-not-called-implicitly-in-placement-new – Retired Ninja Jul 06 '18 at 02:43
  • @RetiredNinja and vu1p3n0x great point. I updated the code. Is it correct now? – ale64bit Jul 06 '18 at 02:44
  • `std::aligned_storage` is C++11. And yes, you are using placement new. Sorry, I didn't mean to imply you weren't using it at all. I was trying to point out that you should use `std::aligned_storage` (which you aren't using) along with placement new (which you are already using). Sorry for not being clear in my original comment. – Cornstalks Jul 06 '18 at 02:44
  • @Cornstalks ah I see. That makes sense. Thanks for the tip! – ale64bit Jul 06 '18 at 02:47
  • Couldn't you use a `vector` for this? `reserve()` the size you want, `resize()` is the equivalent of your `FooPool::New()`, and `clear()` is `FooPool::Release()` – James Picone Jul 06 '18 at 03:29
  • @JamesPicone not sure what do you mean. You can't resize if you don't have a default ctor. Could you provide a gist? – ale64bit Jul 06 '18 at 03:47
  • Didn't realise you don't have a default ctor, sorry. I think you can still do something similar by using `emplace_back` to construct the FooOp. – James Picone Jul 06 '18 at 03:49

1 Answers1

4

I think this code will have similar performance characteristics without requiring you to mess around with placement new and aligned storage:

class FooPool {
public:
    FooPool(int size) {
        pool.reserve(size);
    }

    FooOp* New(const std::vector<char>& v, const std::string& s) {
        pool.emplace_back(v, s); // in c++17: return pool.emplace_back etc. etc.
        return &pool.back();
    }

    void Release() {
        pool.clear();
    }

private:
    std::vector<FooOp> pool;
}

The key idea here being that your FooPool is essentially doing what std::vector does.

James Picone
  • 1,509
  • 9
  • 18