Inapoi
Inainte
Cuprins
Constructor calls
MyType* f = new MyType;
calls new to allocate a
MyType-sized piece of storage, then invokes the MyType constructor
on that storage, what happens if the storage allocation in new fails? The
constructor is not called in
that case, so although you still have an unsuccessfully created object, at least
you haven’t invoked the constructor and handed it a zero this
pointer. Here’s an example to prove it:
//: C13:NoMemory.cpp
// Constructor isn't called if new fails
#include <iostream>
#include <new> // bad_alloc definition
using namespace std;
class NoMemory {
public:
NoMemory() {
cout << "NoMemory::NoMemory()" << endl;
}
void* operator new(size_t sz) throw(bad_alloc){
cout << "NoMemory::operator new" << endl;
throw bad_alloc(); // "Out of memory"
}
};
int main() {
NoMemory* nm = 0;
try {
nm = new NoMemory;
} catch(bad_alloc) {
cerr << "Out of memory exception" << endl;
}
cout << "nm = " << nm << endl;
} ///:~
When the program runs, it does not print
the constructor message, only the message from operator new and the
message in the exception handler. Because new never returns, the
constructor is never called so its message is not printed.
It’s important that nm be
initialized to zero because the new expression never completes, and the
pointer should be zero to make sure you don’t misuse it. However, you
should actually do more in the exception handler than just print out a message
and continue on as if the object had been successfully created. Ideally, you
will do something that will cause the program to recover from the problem, or at
the least exit after logging an error.
In earlier versions of C++ it was
standard practice to return zero from new if storage allocation failed.
That would prevent construction from occurring. However, if you try to return
zero from new with a Standard-conforming compiler, it should tell you
that you ought to throw bad_alloc
instead.
placement new & delete
There are two other, less common, uses
for overloading operator
new.
- You may want to place an
object in a specific location in memory. This is especially important with
hardware-oriented embedded systems where an object may be synonymous with a
particular piece of
hardware.
- You may
want to be able to choose from different allocators when calling
new.
Both of these
situations are solved with the same mechanism: The overloaded operator
new can take more than one argument. As you’ve seen before, the first
argument is always the size of the object, which is secretly calculated and
passed by the compiler. But the other arguments can be anything you want –
the address you want the object placed at, a reference to a memory allocation
function or object, or anything else that is convenient for
you.
The way that you pass the extra arguments
to operator new during a call may seem slightly curious at first. You put
the argument list (without the size_t argument, which is handled
by the compiler) after the keyword new and before the class name of the
object you’re creating. For example,
X* xp = new(a) X;
will pass a as the second argument
to operator new. Of course, this can work only if such an operator
new has been declared.
Here’s an example showing how you
can place an object at a particular location:
//: C13:PlacementOperatorNew.cpp
// Placement with operator new
#include <cstddef> // Size_t
#include <iostream>
using namespace std;
class X {
int i;
public:
X(int ii = 0) : i(ii) {
cout << "this = " << this << endl;
}
~X() {
cout << "X::~X(): " << this << endl;
}
void* operator new(size_t, void* loc) {
return loc;
}
};
int main() {
int l[10];
cout << "l = " << l << endl;
X* xp = new(l) X(47); // X at location l
xp->X::~X(); // Explicit destructor call
// ONLY use with placement!
} ///:~
Notice that operator new only
returns the pointer that’s passed to it. Thus, the caller decides where
the object is going to sit, and the constructor is called for that memory as
part of the new-expression.
Although this example shows only one
additional argument, there’s nothing to prevent you from adding more if
you need them for other purposes.
A dilemma occurs when you want to destroy
the object. There’s only one version of operator delete, so
there’s no way to say, “Use my special deallocator for this
object.” You want to call the destructor, but you don’t want the
memory to be released by the dynamic memory mechanism because it wasn’t
allocated on the heap.
The answer is a very special syntax. You
can explicitly call the destructor, as in
xp->X::~X(); // Explicit destructor call
A stern warning
is in order here. Some people see this as a way to destroy objects at some time
before the end of the scope, rather than either adjusting the scope or (more
correctly) using dynamic object creation if they want the object’s
lifetime to be determined at runtime. You will have serious problems if you call
the destructor this way for an ordinary object created on the stack because the
destructor will be called again at the end of the scope. If you call the
destructor this way for an object that was created on the heap, the destructor
will execute, but the memory won’t be released, which probably isn’t
what you want. The only reason that the destructor can be called explicitly this
way is to support the placement syntax for operator new.
There’s also a placement
operator delete that is only called if a constructor for a placement
new expression throws an exception (so that the memory is automatically
cleaned up during the exception). The placement operator delete has an
argument list that corresponds to the placement operator new that is
called before the constructor throws the exception. This topic will be explored
in the exception handling chapter in Volume
2.
Summary
It’s convenient and optimally
efficient to create automatic objects on the stack, but to solve the general
programming problem you must be able to create and destroy objects at any time
during a program’s execution, particularly to respond to information from
outside the program. Although C’s dynamic memory allocation will get
storage from the heap, it doesn’t provide the ease of use and guaranteed
construction necessary in C++. By bringing dynamic object creation into the core
of the language with new and delete, you can create objects on the
heap as easily as making them on the stack. In addition, you get a great deal of
flexibility. You can change the behavior of new and delete if they
don’t suit your needs, particularly if they aren’t efficient enough.
Also, you can modify what happens when the heap runs out of
storage.
Exercises
Solutions to selected exercises
can be found in the electronic document The Thinking in C++ Annotated
Solution Guide, available for a small fee from
www.BruceEckel.com.
- Create a class Counted
that contains an int id and a static int count. The
default constructor should
begin:
Counted( ) : id(count++) {. It
should also print its id and that it’s being created. The
destructor should print that it’s being destroyed and its id. Test
your class. - Prove to
yourself that new and delete always call the constructors and
destructors by creating an object of class Counted (from Exercise 1) with
new and destroying it with delete. Also create and destroy an
array of these objects on the
heap.
- Create a
PStash object and fill it with new objects from Exercise 1.
Observe what happens when this PStash object goes out of scope and its
destructor is
called.
- Create a
vector< Counted*> and fill it with pointers to new Counted
objects (from Exercise 1). Move through the vector and print the
Counted objects, then move through again and delete each
one.
- Repeat Exercise
4, but add a member function f( ) to Counted that prints a
message. Move through the vector and call f( ) for each
object.
- Repeat
Exercise 5 using a
PStash.
- Repeat
Exercise 5 using Stack4.h from Chapter
9.
- Dynamically
create an array of objects of class Counted (from Exercise 1). Call
delete for the resulting pointer, without the square brackets.
Explain the
results.
- Create an
object of class Counted (from Exercise 1) using new, cast the
resulting pointer to a void*, and delete that. Explain the
results.
- Execute
NewHandler.cpp on your machine to see the resulting count. Calculate the
amount of free store available for your
program.
- Create a
class with an overloaded operator new and delete, both the
single-object versions and the array versions. Demonstrate that both versions
work.
- Devise a test
for Framis.cpp to show yourself approximately how much faster the custom
new and delete run than the global new and
delete.
- Modify
NoMemory.cpp so that it contains an array of int and so that it
actually allocates memory instead of throwing bad_alloc. In
main( ), set up a while loop like the one in
NewHandler.cpp to run out of memory and see what happens if your
operator new does not test to see if the memory is successfully
allocated. Then add the check to your operator new and throw
bad_alloc.
- Create
a class with a placement new with a second argument of type
string. The class should contain a static vector<string>
where the second new argument is stored. The placement new
should allocate storage as normal. In main( ), make calls to
your placement new with string arguments that describe the calls
(you may want to use the preprocessor’s __FILE__ and
__LINE__
macros).
- Modify
ArrayOperatorNew.cpp by adding a static vector<Widget*> that
adds each Widget address that is allocated in operator new and
removes it when it is released via operator delete. (You may need to look
up information about vector in your Standard C++ Library documentation or
in the 2nd volume of this book, available at the Web site.) Create a
second class called MemoryChecker that has a destructor that prints out
the number of Widget pointers in your vector. Create a program
with a single global instance of MemoryChecker and in
main( ), dynamically allocate and destroy several objects and arrays
of Widget. Show that MemoryChecker reveals memory
leaks.
[50]
There is a special syntax called placement new that allows you to call a
constructor for a pre-allocated piece of memory. This is introduced later in the
chapter.
 |
|