Using placement new in a container

785 views Asked by At

I just came across some container implementation in C++. That class uses an internal buffer to manage its objects. This is a simplified version without safety checks:

template <typename E> class Container
{
public:
   Container() : buffer(new E[100]), size(0) {}
   ~Container() { delete [] buffer; }

   void Add() { buffer[size] = E(); size++; }
   void Remove() { size--; buffer[size].~E(); }

private:
   E* buffer;
   int size;
};

AFAIK this will construct/destruct E objects redundantly in Container() and ~Container() if new/delete are not customized. This seems dangerous.

Is using placement new in Add() the best way to prevent dangerous redundant constructor / destructor calls (apart from binding the class to a fully featured pool)?

When using placement new, would new char[sizeof(E)*100] be the correct way for allocating the buffer?

2

There are 2 answers

10
Niall On BEST ANSWER

AFAIK this will construct/destruct E objects redundantly

It would appear so. The newed array already applies the default constructor and the delete[] would call destructor as well for all the elements. In effect, the Add() and Remove() methods add little other than maintain the size counter.

When using placement new, would new char[sizeof(E)*100] be the correct way for allocating the buffer?

The best would be to opt for the std::allocator that handles all of the memory issues for you already.

Using a placement new and managing the memory yourself requires you to be aware of a number of issues (including);

  • Alignment
  • Allocated and used size
  • Destruction
  • Construction issues such as emplacement
  • Possible aliasing

None of these are impossible to surmount, it has just already been done in the standard library. If you are interested in pursuing a custom allocator, the global allocation functions (void* operator new (std::size_t count);) would be the appropriate starting point for the memory allocations.


Without further explanation on the original purpose of the code - a std::vector or a std::array would be far better options for managing the elements in the container.

0
Persixty On

There's a number of issues with the code. If you call Remove() before calling Add() you will perform assignment to a destructed object.

Otherwise the delete[] buffer will call the destructor of 100 objects in the array. Which may have been called before.

Here's a valid program:

#include <iostream>

int counter=0;

class Example {

    public:
        Example():ID(++counter){
           std::cout<<"constructing "<<ID<<std::endl;
        }
        ~Example(){
            std::cout<<"destructing "<<ID<<std::endl;
            ID=-1;
        }
    private:


      int ID;
};

template <typename E> class Container
{
public:
   Container() : buffer(new char [100*sizeof(E)]), size(0) {}
   ~Container() {
        for(size_t i=0;i<size;++i){
            reinterpret_cast<E*>(buffer)[i].~E();
        }
        delete []  buffer; 
    }

   void Add() { new (buffer+sizeof(E)*size) E(); size++; }
   void Remove() { reinterpret_cast<E*>(buffer)[--size].~E(); }

private:
   void* buffer;
   size_t size;
};


int main() {
    Container<Example> empty;

    Container<Example> single;
    Container<Example> more;

    single.Add();

    more.Add();

    more.Remove();

    more.Add();
    more.Add();
    more.Remove();

    return 0;
}