Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Sutter’s Mill: To New, Perchance To Throw [1] (Part 1 of 2)


March 2001/Sutter's Mill


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

  • 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.
  • 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
//
// grab a sufficient amount of
// raw memory
void* p = ::operator new(sizeof(T));
    
// construct the T at address p,
// probably calls
// ::operator new(std::size_t, void*)
//     throw()
new (p) T;  

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[]):

// usual plain old boring new
// usage: new T
void*
::operator new(std::size_t size)
   throw(std::bad_alloc);
    
// nothrow new
// usage: new (std::nothrow) T
void*
::operator new(std::size_t size,
   const std::nothrow_t&) throw();
    
// in-place or "put-it-there" new
// usage: new (ptr) T
void*
::operator new(std::size_t size,
   void* ptr) throw();

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.

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

// Example 2: Using various
// indigenous and user-supplied
// overloads of new
//

// 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 (FastMemory()) T;
    
// calls some user-supplied
//
// operator new(std::size_t,
//    int, double, const char*)
//
// (or something similar, with
// argument type conversions)
new (42, 3.14159, "xyzzy") T;
    
// probably calls the standard or
// some user-supplied
//
// ::operator new(std::size_t,
//   const std::nothrow_t&) throw()
//
new (std::nothrow) T;

In each case shown in Examples 1 and 2, the parameters inside the parentheses 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:

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

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:

  • 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.
  • 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

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

void* p3 = /* some valid memory
              that's big enough for  
              a Derived */

// ERROR: no match
new (p3) Derived;

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

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, 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++ [2].)

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++ [3], but the other points are as important.

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

  • 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 such as a fixed-size allocation strategy:
  • // 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 provide a class-specific plain new, you won’t be able to use the class with any code that tries to dynamically allocate objects the usual way.

  • 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>.

  • 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 the normal new:

    // "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;
      }
    }
    

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] Herb Sutter. Exceptional C++ (Addison-Wesley, 2000).

[3] Scott Meyers. Effective C++, Second Edition (Addison-Wesley, 1997).

Herb Sutter is an independent consultant and secretary of the ISO/ANSI C++ standards committee. He can be reached at [email protected].


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.