Am avut 371708 vizite de la lansarea siteului.




Inapoi        Inainte       Cuprins

Constructor calls

Considering that

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.

  1. 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.
  2. 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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. Repeat Exercise 5 using a PStash.
  7. Repeat Exercise 5 using Stack4.h from Chapter 9.
  8. 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.
  9. Create an object of class Counted (from Exercise 1) using new, cast the resulting pointer to a void*, and delete that. Explain the results.
  10. Execute NewHandler.cpp on your machine to see the resulting count. Calculate the amount of free store available for your program.
  11. Create a class with an overloaded operator new and delete, both the single-object versions and the array versions. Demonstrate that both versions work.
  12. 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.
  13. 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.
  14. 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).
  15. 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.

Home   |   Web Faq   |   Radio Online   |   About   |   Products   |   Webmaster Login

The quality software developer.™
© 2003-2004 ruben|labs corp. All Rights Reserved.
Timp de generare a paginii: 17583 secunde
Versiune site: 1.8 SP3 (build 2305-rtm.88542-10.2004)