Channels ▼
RSS

C/C++

Calling Constructors with Placement New


In C++, classes are usually the best tool for modeling memory-mapped devices. You can use a class to hide messy details, such as device register layouts and bit masks, behind a simpler and more robust interface.

A "constructor" is a special class member function that provides guaranteed initialization for objects of its class type. Using constructors to initialize objects is common practice in C++. C++ programmers should reasonably expect classes for memory-mapped devices to do initialization via constructors, unless there's some compelling reason to do otherwise.

In many embedded systems, the appropriate way to initialize a device is to put it into an inactive state. For example, the constructor for a programmable timer might simply make sure that the timer is disabled, as in:

class timer_type
    {
public:
    ~~~
    timer_type() { disable(); }
    ~~~
    };

The problem with memory-mapped devices is getting them to execute automatically. In the rest of this article, I explore how to solve this problem.

Recapping the Problem

With most C and C++ compilers, you can name a memory-mapped object using a standard extern declaration such as:

extern timer_type the_timer;

and then use the linker to map the_timer to the desired address. However, this declaration is not a definition, so the compiler doesn't generate code to allocate storage or call a constructor.

Some C and C++ compilers provide a nonstandard language extension that lets you position an object at a specified memory address, such as:

timer_type the_timer @ 0xFFFF6000;

With most compilers that support this sort of declaration, this isn't a definition, so the compiler won't generate a constructor call for this, either.

Defining a pointer or reference to a memory-mapped object, such as:

timer_type &the_timer
    = *reinterpret_cast<timer_type *>(0xFFFF6000);

has the same initialization problem as the previous object declarations. This reference definition doesn't define the memory-mapped object itself. Here, too, the compiler won't generate a constructor call applied to the memory-mapped object.

If the compiler won't call a constructor implicitly, why not just write an explicit call, say, immediately after the object or reference declaration? That is, if you declare the timer object as:

extern timer_type the_timer;

why not write an explicit constructor call to go with it, such as:

the_timer.timer_type();     // ?

Because it won't compile. C++ won't let you call a constructor as if it were any other class member function. In taking on the job of generating constructor calls automatically, C++ denies you the ability to do it yourself. Well, almost.

In truth, although you can't call a constructor using the usual member function call syntax, you can call a constructor using a particular form of new-expression known as "placement new-expression." To appreciate how it works, let's first review new-expressions in general.

New-expressions

In C++, you typically allocate dynamic storage using a new-expression such as:

pt = new T; 

where T is a type and pt is an object of type "pointer to T." When T is a class type, the new-expression not only allocates storage, but also invokes a constructor. Thus, using new is generally preferable to using the standard malloc function. Whereas malloc allocates raw storage of indeterminate value, new can create objects with coherent initial values.

A new-expression allocates memory by calling a function named operator new. Each C++ environment provides a default global allocation function declared as:

void *operator new(std::size_t n);

As with malloc, the argument to operator new is the size (in bytes) of the storage request, and the return value is the address of the allocated storage. However, operator new reports failure differently from malloc. Whereas malloc returns a null pointer if it can't allocate the requested storage, operator new throws an exception [1].

Thus, for a class type T, a new-expression such as:

pt = new T; 

translates more-or-less into something like:

pt = static_cast(operator new(sizeof(T)));
pt->T();

The first statement acquires storage for a T object by calling operator new, and converts the address of that storage from type void * to type T *. The second initializes the storage by applying T's default constructor. As I mentioned earlier, a C++ compiler won't let you write this explicit constructor call, but it's happy to do it for you.

If class T has a constructor that accepts arguments, you can get the new-expression to call that constructor by providing the constructor arguments as part of the new-expression, as in:

pt = new T (x, y, z);.

In this case, the new-expression translates more-or-less into something like:

pt = static_cast(operator new(sizeof(T)));
pt->T(x, y, z);

Overloading operator new

As with any other function, you can overload operator new by simply declaring additional functions with the same name but different parameter types. For example, in addition to the standard allocation function:

void *operator new(std::size_t n);

you might declare:

void *operator new(std::size_t n, other_info i);

where other_info is some user-defined type for conveying additional information to the allocation function. That's simple enough, but how can you get a new-expression to use this alternative allocation function?

The problem is finding some way to pass the additional argument to operator new. You can't add a parenthesized argument list after the type name in the new-expression because the compiler will interpret that list as arguments to a constructor, not as arguments to an operator new. That is:

pt = new T (x); 

passes x as an argument to a T constructor, not as an additional argument to operator new.

Rather, you must squeeze the additional argument(s) to the allocation function into some other spot in the new-expression. That spot is between the keyword new and the allocated type. For example, the new-expression in:

other_info info;
~~~
pt = new (info) T;

specifies info as an additional argument to operator new. As always, the new-expression uses sizeof(T) as the first argument to operator new. Thus, the new-expression results in a call to:

operator new(sizeof(T), info).

Compilers apply the usual rules for argument matching in overload resolution to find an allocation function that will accept this assembled argument list [2]. The new-expression won't compile if no operator new is visible that will accept the given arguments.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video