einpoklum einpoklum - 1 month ago 5
C++ Question

How should I use placement new with a custom allocation API?

I'm working on some memory space with custom allocation and deletion, which happen using a malloc-like interface which I don't control (i.e. allocate n bytes or free an allocated ptr). So, nothing like delete[].

Now, I want to construct an array of T's. I get the space for that with

auto space_ptr = magic_malloc(n*sizeof(T))
. Now I want to do something like array-placement-new to construct the n elements in-place. How should I do that? ... or should I just loop from 1 to n and construct single T's?

Note:


  • I'm ignoring alignment issues here (or rather, assuming
    alignof(T)
    divides
    sizeof(T)
    ). If you want to address alignment, that would be even better, but you may ignore it for simplicity.

  • C++11 code welcome (preferred, in fact), but no C++14/17.


Answer

I will assume your memory is sufficiently aligned for your T. You probably want to check that.

The next problem is exceptions. We should really write two versions, one with the possibility that constructing will cause an exception, and one without.

I'll write the exception safe version.

template<class T, class...Args>
T* construct_n_exception_safe( std::size_t n, void* here, Args&&...args ) {
  auto ptr = [here](std::size_t i)->void*{
    return static_cast<T*>(here)+i;
  };
  for( std::size_t i = 0; i < n; ++i ) {
    try {
      new(ptr(i)) T(args...);
    } catch( ... ) {
      try {
        for (auto j = i; j > 0; --j) {
          ptr(j-1)->~T();
        }
      } catch ( ... ) {
        exit(-1);
      }
      throw;
    }
  }
  return static_cast<T*>(here);
}

and the not exception safe version:

template<class T, class...Args>
T* construct_n_not_exception_safe( std::size_t n, void* here, Args&&...args ) {
  auto ptr = [here](std::size_t i)->void*{
    return static_cast<T*>(here)+i;
  };
  for(std::size_t i = 0; i < n; ++i) {
    new (ptr(i)) T(args...);
  }
  return static_cast<T*>(here);
}

You can do a tag-dispatch based system to pick between them depending on if constructing T from Args&... throws or not. If it throws, and ->~T() is non-trivial, use the exception-safe one.

C++17 exposes some new functions to do exactly these tasks. They probably handle corner cases mine don't.


If you are trying to emulate new[] and delete[], if T has a non-trivial dtor you'll have to embed how many T you created in the block.

The typical way to do this is to ask for extra room for the count at the front of the block. Ie, ask for sizeof(T)*N+K, where K may be sizeof(std::size_t).

Now in your new[] emulator, stuff N into the first bit, and then call construct_n on the block right after it.

In delete[], subtract sizeof(std::size_t) from the passed in pointer, read N and then destroy the objects (from right-to-left to mirror construction order).

All this will need careful try-catch.

If, however, ~T() is trivial, both your emulated new[] and delete[] do not store that extra std::size_t nor do they read it.

(Note that this is how to emulate new[] and delete[]. How exactly new[] and delete[] work is implementation dependant. I'm just sketching out one way you can emulate them, it may not be compatible with how they work on your system. For example, some ABIs might always store N even if ->~T() is trivial, or a myriad of other variations.)


As noted by OP, you might also want to check for trivial construction prior to bothering with the above.