Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm working on some memory space with custom allocation and deletion, which are made using a malloc-like interface, that is not under my control (i.e. opaque C-style functions for "allocate n bytes" or "free an allocated ptr"). So, nothing like new or delete.

Now, I want to construct an array of T's. I get the space for that with auto space_ptr = custom_alloc(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.
like image 434
einpoklum Avatar asked Nov 07 '16 19:11

einpoklum


People also ask

Does placement new allocate?

Placement new is a variation new operator in C++. Normal new operator does two things : (1) Allocates memory (2) Constructs an object in allocated memory. Placement new allows us to separate above two things. In placement new, we can pass a preallocated memory and construct an object in the passed memory.

Does placement new call destructor?

¶ Δ Probably not. Unless you used placement new , you should simply delete the object rather than explicitly calling the destructor.

How do you use the new operator?

Use of the new operator signifies a request for the memory allocation on the heap. If the sufficient memory is available, it initializes the memory and returns its address to the pointer variable. The new operator should only be used if the data object should remain in memory until delete is called.

How do you delete a placement call?

Placement deleteIt is not possible to call any placement operator delete function using a delete expression. The placement delete functions are called from placement new expressions. In particular, they are called if the constructor of the object throws an exception.


1 Answers

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.

like image 51
Yakk - Adam Nevraumont Avatar answered Oct 08 '22 09:10

Yakk - Adam Nevraumont