Using The C++ Placement New Operator

Overview

Using C++11's rvalue references, it is an interesting exercise to see how such can be used with placement new and the new alignas() functionality. This post implements a class that has the ability to conditionally store an object without allocating space for the object from the heap. To accomplish this, the object holds a character array capable of holding an instance of its template argument type T and uses placement new to assign the array to the object.

Aside: Unfortunately the GCC version 4.7.0 snapshot 20111029 C++ compiler does not yet appear to support alignas(). I have left an alignas() commented out so it can be revisited when such is supported. With C++98 it is up to the programmer to ensure that a non-heap allocated character array is properly aligned in memory to store some type T. Although such is beyond the scope of this post, it should soon be moot when compilers support such! 🙂

For those familiar with Boost, the code presented herein is conceptually similar to the functionality provided by boost::optional.


Conditionally Storing An Object Without Using The Heap

In order to conditionally store an object without using the heap, one will need a bool to indicate whether or not the object exists and sufficient memory to hold (a properly aligned object):

template <typename T>
class optional
{
  private:
    /*alignas(T)*/ char t_[sizeof(T)];
    bool created_;
};

where t_ is a character array capable of holding a T. Now only if there was a way to invoke new using &t_[0]. There is! It is a C++98 feature called placement new. Placement new allows one to write an operator new that takes customized function arguments. Conveniently, the C++98 standard defines an operator new overload that simply accepts an address where the object will be instantiated in RAM. When this is done, one must ensure enough space exists at that address, that the data is properly aligned, and manually call the destructor when the object is destroyed. Since there is no corresponding placement delete operator, the programmer is responsible for explicitly calling the destructor and performing any necessary clean-up operations.

Given the t_ array above one could create an default object of type T where the t_ array is located in RAM by invoking:

T* p = new(t_) T();

and later the object can be destroyed by explicitly invoking the object's destructor:

p->~T();

Cool, eh?!!!

NOTE: The destructor for a type can only be called if a placement new operator was used to create the instance. Although it can be done, never, ever explicitly call the destructor for an object that was not created with placement new!

So the concept this post implements is this: if created_ is true then an instance of T would have been previously instantiated at t_ and if created_ is false no instance of T exists at T.


Handling Instantiations

Let's start by writing the member functions needed to properly instantiate objects of type T located at t_. To do this, the following cases need to be handled:

  • default construction of T
  • copy construction of T
  • move construction of T
  • copy assignment of T
  • move assignment of T
  • destruction of T

Note that these concern T. Since the class template is optional<T> these operations must be defined to support the above:

  • default construction of optional<T>
  • copy construction of optional<T>
  • move construction of optional<T>
  • copy assignment of optional<T>
  • move assignment of optional<T>
  • destruction of optional<T>

Fortunately, the number of cases to handle can be simplified by writing some protected member functions by first noting:

  • Should the object not previously exist for any constructor or assignment invocation, then it must be created with the default value or by using the provided value.
  • Should the object previously exist for any assignment invocation, then it is sufficient to assign the provided value to the object.

Thus, the following member functions can handle all of the aforementioned cases:

protected:
  // Create T using the default constructor or assign default
  // value if already created...
  T& create_or_default();

  // Create T using value or assign value if already created...
  T& create_or_assign(T const& value);

  // Create T using value or assign value if already created...
  T& create_or_assign(T&& value);

  // Destroy T (invokes destructor and sets created_ to false)...
  void destroy();

  // Properly invokes a create_or_*() or destroy() function...
  void create(optional<T> const& o);

  // Properly invokes a create_or_*() or destroy() function...
  void create(optional<T>&& o);

With such the traditional constructors are easily written (note the use of std::move):

public:
  struct none { };

  optional()
    : created_(false)
  {
  }

  optional(none)
    : created_(false)
  {
  }

  optional(optional<T> const& o)
    : created_(false)
  {
    create(o);
  }

  optional(optional<T>&& o)
    : created_(false)
  {
    create(std::move(o));
  }

  optional(T const& t)
    : created_(false)
  {
    create_or_assign(t);
  }

  optional(T&& t)
    : created_(false)
  {
    create_or_assign(std::move(t));
  }

  optional<T>& operator =(none)
  {
    destroy();
    return *this;
  }

  optional<T>& operator =(T const& t)
  {
    create_or_assign(t);
    return *this;
  }

  optional<T>& operator =(T&& t)
  {
    create_or_assign(std::move(t));
    return *this;
  }

  ~optional()
  {
    destroy();
  }

To simplify the implementation of the protected helper functions above, let's define the following functions which all assume that an object of type T has been created:

public:
  T& get()
  {
    return *static_cast<T*>(static_cast<void*>(t_));
  }

  // Until g++ supports this rvalues...
  T&& get_as_rvalue()
  {
    return std::move(*static_cast<T*>(static_cast<void*>(t_)));
  }

  T const& get() const
  {
    return *static_cast<T const*>(static_cast<void const*>(t_));
  }

  T* ptr()
  {
    return static_cast<T*>(static_cast<void*>(t_));
  }

  T const* ptr() const
  {
    return static_cast<T const*>(static_cast<void const*>(t_));
  }

It is necessary to use static_cast twice to properly coerce the t_ pointer into a pointer to a different type by converting it to a void* first. By doing these casts in these functions, all other functions will not need to use any casts! 🙂


Implementing create_or_default()

If the object has already been created, then assign the default value to it. If not, then create it by invoking its default constructor using placement new:

T& create_or_default()
{
  if (created_)
  {
    T& t = get();
    t = T();
    return t;
  }
  else
  {
    T& retval = *(new(t_) T());
    created_ = true;
    return retval;
  }
}


Implementing create_or_assign()

If the object has already been created, then assign the value pass in to it. If not, then create it by invoking its copy/move constructor using placement new:

T& create_or_assign(T const& value)
{
  if (created_)
  {
    T& t = get();
    t = value;
    return t;
  }
  else
  {
    T& retval = *(new(t_) T(value));
    created_ = true;
    return retval;
  }
}

T& create_or_assign(T&& value)
{
  if (created_)
  {
    T& t = get();
    t = std::move(value);
    return t;
  }
  else
  {
    T& retval = *(new(t_) T(std::move(value)));
    created_ = true;
    return retval;
  }
}


Implementing destroy()

If the object has already been created, call its destructor and set created_ to false. Otherwise do nothing:

void destroy()
{
  if (created_)
  {
    T* t = ptr();
    t->~T();
    created_ = false;
  }
}


Implementing create()

Both copy and move versions of create() have four cases to consider corresponding to the values of this->created_ and o.created_ with o being passed in to the function. The four cases are converted into a two-bit number so they can be efficiently handled using a switch statement:

void create(optional<T> const& o)
{
  switch ((created_ << 1) | o.created_)
  {
    case 1:
    case 3:
      create_or_assign(o.get());
      break;

    case 2:
      destroy();
      break;

    default: // i.e., case 0:
      break;
  }
}

void create(optional<T>&& o)
{
  switch ((created_ << 1) | o.created_)
  {
    case 1:
    case 3:
      create_or_assign(o.get_as_rvalue());
      break;

    case 2:
      destroy();
      break;

    default: // i.e., case 0:
      break;
  }
}


Adding Some Convenience Member Functions

To facilitate the use of class types and the ability to test optional<T> instances in boolean statements to determine if it holds an instance of type T, the following public member functions are added:

operator bool() const
{
  return created_;
}

bool operator !() const
{
  return !created_;
}

T& operator *()
{
  return get();
}

T const& operator *() const
{
  return get();
}

T* operator ->()
{
  return ptr();
}

T const* operator ->() const
{
  return ptr();
}


Using optional<T>

Now optional can be used as follows:

#include <iostream>

int main()
{
  using namespace std;

  optional<int> a;
  cout << (a ? "a is set" : "a is not set") << endl;
  a = 5;
  cout << (a ? "a is set" : "a is not set") << endl;
  a = optional<int>::none();
  cout << (a ? "a is set" : "a is not set") << endl;
}

which outputs:

$ ./a.out
a is not set
a is set
a is not set
$


Closing Comments

Fortunately issues of alignment are (thank)fully addressed in C++11 and so we are only waiting for compilers to add support for those C++11 features. This post demonstrates how one can conditionally create/destroy objects not located on the heap at will while fully leveraging full copy/move semantics. This is very powerful as it allows instances of a class to defer member object instantiation without using the heap. Avoiding the use of the heap enables more efficient code to be written especially when using custom memory allocators or objects that must lazily construct their member variables.

Far too often, code is written to use the heap only to store pointers or other small objects requiring an additional pointer indirection just to access them. Using C++98's placement new operator, C++11's alignment features (when finally implemented), and C++11's move semantics, one can choose to elide a layer of indirection by reserving enough space to hold an object and instantiating it when needed in place. The trade-off is between a small amount of additional adjacent RAM than otherwise required by an object versus the space overhead of allocating object(s) on the heap and using an additional pointer located elsewhere in RAM to access the data. It is reasonable to suggest that the former is likely to run much faster (i.e., with respect to CPU instructions, cache performance, the avoidance of slow heap functions) than the latter especially when small objects are used.

3 Replies to “Using The C++ Placement New Operator”

  1. I have been contacted by others kindly pointing out that the GCC does support alignment. True, but my focus of the above post (as with other posts) is standards-conforming code. This was the reason why I left alignas commented out and said that alignment issues were beyond the scope of this post.

    If you are using GCC, know that you can write a simple macro that will allow the use of alignas (given in the code but commented out above). The macro code be defined as follows:

    #if defined(__GNUC__) && (__GNUC__ >= 4) && defined(__cplusplus)
      // URL references...
      // http://gcc.gnu.org/onlinedocs/gcc-4.6.2/gcc/Alignment.html#Alignment
      // http://gcc.gnu.org/onlinedocs/gcc-4.6.2/gcc/Variable-Attributes.html#Variable-Attributes
      #define alignas(type) __attribute__ ((aligned( __alignof__ (type) )))
    #else
      //#define alignas(type)
      #error no definition for alignas
    #endif

    The unfortunate reality with such code is that it is compiler specific and this is something that should be standardized. Fortunately, it is in C++11 and we are merely waiting for GCC to implement the alignment portions of the language.

  2. Why did you obfuscate the code with a switch and binary arithmetic?
    if (o.created_)
    create_or_assign(o.get());
    else if (created_)
    destroy();

    would be simpler imho. But may be there are deeper reasons beyond my understanding.

    1. As written, either way works and your point is accepted, well taken and very welcome! Thanks for pointing it out too –I had missed editing it to be "simpler" during my edit pass! 🙂

      I don't know if I have an answer other than that is the way I wrote it and the code I had was restructured a bit before being included in the blog article. Certainly, your code is shorter and more typical of everyday code. I must respectfully disagree that it is truly obfuscated as the code should be easily understood by any C/C++ programmer (esp. since it only involves four states). Just as my code fails to show a nice if…else statement and could be considered to be obfuscated, writing the if…else statement hides some of the clarity of how all states are being handled (as compared to the switch code) –so some might argue the latter obfuscates the former. Certainly, the switch clause clearly provides the entire table on how to handle all four states by processing one value. The if…else solution is less clear without some study of the two tests, their variables, and the relationship(s) between them. Suppose someone was to change how code was to handle the creates, assignments, and destructions, then the switch table is likely to be more easily and correctly modified then the if…else version as the latter could require more careful thought before editing.

      It could well be one of those "six of one and half-a-dozen of the other" kind of things! It is possible that different coding policies/conventions could well have an effect on deciding which one is "better" –as both are equivalent. (Some may prefer the terse if solution, others may prefer the state machine styled version, etc.)

Leave a Reply

Your email address will not be published. Required fields are marked *