To New Perchance To... Part 1

Home Blog Talks Books & Articles Training & Consulting

Prev
Up
Next

On the
blog
RSS feed November 4: Other Concurrency Sessions at PDC
November 3
: PDC'09: Tutorial & Panel
October 26: Hoare on Testing
October 23
: Deprecating export Considered for ISO C++0x

To New, Perchance To Throw[1], Part 1

This article appeared in C/C++ Users Journal, 19(3), March 2001.

 

In this column and the next, I want to state and justify just two main pieces of advice:

bullet

Any class that provides its own class-specific operator new(), or operator new[](), should also provide corresponding class-specific versions of plain new, in-place new, and nothrow new. Doing otherwise can cause needless problems for people trying to use your class.

bullet

Avoid using new(nothrow), and make sure that when you're checking for new failure you're really checking what you think you're checking.

Some of this advice may be surprising, so let's examine the reasons and rationale that lead to it. This article focuses on the first point; next time we'll consider the second. For simplicity, I'm not going to mention the array forms of new specifically; what's said about the single-object forms applies correspondingly to the array forms.

In-Place, Plain, and Nothrow New

The C++ standard provides three forms of new, and allows any number of additional overloads. One useful form is in-place new, which constructs an object at an existing memory address without allocating new space. For example:

// Example 1: Using in-place new, an "explicit constructor call"
//
void* p = ::operator new( sizeof(T) );
           // grab a sufficient amount of raw memory

new (p) T; // construct the T at address p, probably calls
           // ::operator new(std::size_t, void*) throw()

The standard also supplies "plain old new" which doesn't take any special additional parameters, and nothrow new which does. Here's a complete list of the operator new overloads supplied in Standard C++:

// The standard-provided overloads of operator new
// (there are also corresponding ones for array new[]):
//
void* ::operator new(std::size_t size) throw(std::bad_alloc);
      // usual plain old boring new
      // usage: new T

void* ::operator new(std::size_t size, const std::nothrow_t&) throw();
      // nothrow new
      // usage: new (std::nothrow) T

void* ::operator new(std::size_t size, void* ptr) throw();
      // in-place (or "put-it-there") new
      // usage: new (ptr) T

Programs are permitted to replace all but the last form with their own versions. All of these standard functions live in global scope, not in namespace std. In brief, Table 1 summarizes the major characteristics of the standard versions of new:

Standard new version

Additional parameter

Performs allocation

Can fail [2]

Throws

Can be replaced in user programs

Plain

None

Yes

Yes (throws)

std::
bad_alloc

Yes

Nothrow

std::
nothrow_t

Yes

Yes
(returns null)

No

Yes

In-Place

void*

No

No

No

No

Table 1: Comparison of the standard versions of new

Here is an example showing some ways to use these versions of new:

// Example 2: Using various indigenous and user-supplied
// overloads of new
//
new (FastMemory()) T;
    // calls some user-supplied
    // operator new(std::size_t, FastMemory&)
    // (or something similar, with argument type conversions),
    // presumably to select a custom memory arena

new (42, 3.14159, "xyzzy") T;
    // calls some user-supplied
    // operator new(std::size_t, int, double, const char*)
    // (or something similar, with argument type conversions)

new (std::nothrow) T;
    // probably calls the standard or some user-supplied
    // operator ::new(std::size_t,
    // const std::nothrow_t&) throw()

In each case shown in Examples 1 and 2, the parameters inside the brackets in the new-expression turn into additional parameters tacked onto the call to operator new(). Of course, unlike the case in Example 1, the cases in Example 2 probably do allocate memory in one way or another, rather than use some existing location.

Class-Specific New

Besides letting programs replace some of the global operators new, C++ also lets classes provide their own class-specific versions. When reading Examples 1 and 2, did you notice the word "probably" in two of the comments? They were:

new (p) T; // construct the T at address p, probably calls
           // ::operator new(std::size_t, void*) throw()

new (std::nothrow) T;
          // probably calls the standard or some user-supplied
          // operator ::new(std::size_t,
          //                const std::nothrow_t&) throw()

The "probably" is because the operators invoked may not necessarily be the ones at global scope, but may be class-specific ones. To understand this clearly, notice two interesting interactions between class-specific new and global new:

bullet

Although you can't directly replace the standard global in-place new, you can write your own class-specific in-place new that gets used instead for that class.

bullet

You can add class-specific nothrow new with or without replacing the global one.

So in the two code lines repeated above, it's possible that T (or one of T's base classes) provides its own versions of one or both operators new being invoked here, and if so then those are the ones that will get used.

Here is a simple example of providing class-specific new, where we just provide our own versions of all three global flavors:

// Example 3: Sample class-specific versions of new
//
class X
{
public:
  static void* operator new( std::size_t ) throw();           // 1
  static void* operator new( std::size_t,
                             const std::nothrow_t& ) throw(); // 2
  static void* operator new( std::size_t, void* ) throw();    // 3
};

X* p1 = new X;                // calls 1

X* p2 = new (std::nothrow) X; // calls 2

void* p3 = /* some valid memory that's big enough for an X */
new (p3) X;                   // calls 3 (!)

I put an exclamation point after the third call to again draw attention to the funky fact that you can provide a class-specific version of in-place new even though you can't replace the global one.

A Name Hiding Surprise

This, finally, brings us to the reason I've introduced all of this machinery in the first place, namely the name hiding problem:

// Example 4: Name hiding "news"
//
class Base
{
public:
  static void* operator new( std::size_t,
                             const FastMemory& ); //4
};

class Derived : public Base
{
  // ...
};

Derived* p1 = new Derived;                // ERROR: no match

Derived* p2 = new (std::nothrow) Derived; // ERROR: no match

void* p3 = /* some valid memory that's big enough for a Derived */
new (p3) Derived;                         // ERROR: no match

Derived* p4 = new (FastMemory()) Derived; // calls 4

Most of us are familiar with the name hiding problem in other contexts, such as a name in a derived class hiding one in the base class, but it's worth remembering that name hiding can crop up for operator new too. Remember how name lookup works: In brief, the compiler starts in the current scope (here, in Derived's scope), and looks for the desired name (here, operator new); if no instances of the name are found, it moves outward to the next enclosing scope (in Base's and then global scope) and repeats. Once it find a scope containing at least one instance of the name (in this case, Base's scope), it stops looking and works only with the matches it has found, which means that further outer scopes (in this case, the global scope) are not considered and any functions in them are hidden; instead, the compiler looks at all the instances of the name it has found, selects a function using overload resolution, and finally checks access rules to determine whether the selected function can be called. The outer scopes are ignored even if none of the overloads found has a compatible signature, meaning that none of them could possibly be the right one; the outer scopes are also ignored even if the signature-compatible function that's selected isn't accessible. That's why name hiding works the way it does in C++. (For more details about name lookup and name hiding, see Item 34 in Exceptional C++.[3])

What this means is that if a class C, or any of its base classes, contains a class-specific operator new() with any signature, that function will hide all of the global ones and you won't be able to write normal new-expressions for C that intend to use the global versions. The only reasonable way to re-enable the global ones is for C to provide the necessary passthrough functions itself - calling code must otherwise know to write globally-qualified new-expressions to select a global operator new(),

This leads to a few interesting conclusions, best expressed as a coding and design guideline. Scott Meyers covers part of the first bullet in Item 9 of Effective C++[4], but the other points are as important.

Guideline: If you provide any class-specific new, then also:

bullet

Always provide class-specific plain (no-extra-parameters) new. The class-specific version should almost always preserve the global version's semantics, so declare it with an exception specification of throw(std::bad_alloc), and prefer to implement it in terms of the global version unless you really need to put in some special handling:

// Preferred implementation of class-specific plain new.
//
void* C::operator new( std::size_t s )
         throw( std::bad_alloc )
{
  return ::operator new( s );
}

Note that you might be calling a replaced global version, rather than the standard's default one, but that's normally a good thing: In most cases, a replaced global operator new() exists for debugging or heap instrumentation reasons, and it's desirable to reflect such behavior in class-specific versions.

If you don't do this, you won't be able to use the class with any code that tries to dynamically allocate objects the usual way.

bullet

Always provide class-specific in-place new. This should always preserve the global version's semantics, so declare it to throw nothing, and implement it in terms of the global version:

// Preferred implementation of class-specific in-place new.
//
void* C::operator new( std::size_t s, void* p )
         throw()
{
  return ::operator new( s, p );
}

If you don't do this, you will surprise (and break) any calling code that tries to use in-place new for your class. In particular, standard library container implementations commonly use in-place construction and expect such in-place construction to work the usual way; this is, after all, the only way to make an explicit constructor call in C++. Unless you write the above, you probably won't be able to use even a std::vector<C>.

bullet

Consider providing class-specific nothrow new in case some users of your class do want to invoke it, otherwise it will be hidden by other class-specific overloads of new. Either implement it using the global nothrow new:

// "Option A" implementation of class-specific nothrow new.
// Favors consistency with global nothrow new. Should have
// the same effect as Option B.
//
void* C::operator new( std::size_t s,
                       const std::nothrow_t& n )
         throw()
{
  return ::operator new( s, n );
}

or, to ensure consistent semantics with the normal new (which the global nothrow new ought to do, but what if someone replaced it in a way such that it doesn't?), implement it in terms of that:

// "Option B" implementation of class-specific nothrow new.
// Favors consistency with corresponding class-specific
// plain new. Should have the same effect as Option A.
//
void* C::operator new( std::size_t s,
                       const std::nothrow_t& )
         throw()
{
  try
  {
    return C::operator new( s );
  }
  catch( ... )
  {
    return 0;
  }
}

Note that these passthroughs can't be simulated with a using declaration, such as "using ::operator new;". The only place such a using declaration would be helpful would be inside the definition for class C, but it's illegal there; within a class, you can only write using declarations that bring in names from base classes, not other names such as global names or names from other classes. Requiring that the calling code add the using declaration itself would not only be onerous, but wouldn't even help because we may not be able to modify it; some of the calling code would be inside what to us are read-only modules like third-party libraries, or even inside the C++ standard library if we were trying to provide the standard containers with access to in-place new.

Summary

Next time, we'll delve deeper into the question of what operator new() failures mean, and how best to detect and handle them. Along the way, we'll see why it can be a good idea to avoid using new(nothrow) - perhaps most surprisingly, we'll also see that, on certain popular real-world platforms, memory allocation failures usually don't even manifest in the way the standard says they must! Stay tuned.

 

Notes

1. With apologies to the Bard - meaning either Shakespeare or Bacon, depending which version of history you happen to prefer.

2. After operator new() is done, the object's constructor will be invoked and of course that constructor operation might still fail, but we're not worried about that here. Here we're analyzing specifically whether or not operator new() itself can fail.

3. Herb Sutter. Exceptional C++ (Addison-Wesley, 2000).

4. Scott Meyers. Effective C++, 2nd edition (Addison-Wesley, 1997).

Copyright © 2009 Herb Sutter