If you're going to overload operator new, make sure you get the right one, and that the compiler picks the right ones as well.
March 01, 2001
URL:http://drdobbs.com/sutters-mill-to-new-perchance-to-throw-1/184401369
In this column and the next, I want to state and justify just two main pieces of advice:
Some of this advice may be surprising, so lets examine the reasons and rationale that lead to it. This article focuses on the first point; next time well consider the second. For simplicity, Im not going to mention the array forms of new specifically; whats said about the single-object forms applies correspondingly to the array forms.
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 doesnt take any special additional parameters, and nothrow new, which does. Heres 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.
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:
So in the two code lines repeated above, its possible that T (or one of Ts 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 cant replace the global one.
This, finally, brings us to the reason Ive 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 its 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 Deriveds 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 Bases and then global scope) and repeats. Once it find a scope containing at least one instance of the name (in this case, Bases 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 thats selected isnt accessible. Thats 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 wont 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:
// 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 Standards default one, but thats normally a good thing: In most cases, a replaced global operator new exists for debugging or heap instrumentation reasons, and its desirable to reflect such behavior in class-specific versions.
If you dont do provide a class-specific plain new, you wont be able to use the class with any code that tries to dynamically allocate objects the usual way.
// 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 dont 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 wont be able to use even a std::vector<C>.
// "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 doesnt?), 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; } }
Next time, well delve deeper into the question of what operator new failures mean, and how best to detect and handle them. Along the way, well see why it can be a good idea to avoid using new(nothrow) perhaps most surprisingly, well also see that, on certain popular real-world platforms, memory allocation failures usually dont even manifest in the way the Standard says they must! Stay tuned.
[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].
Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.