Выбрать главу

  ~Cat() { cout << "~Cat()" << endl; }

  void g() {}

};

class Dog {

public:

  void* operator new[](size_t) {

    cout << "Allocating a Dog" << endl;

    throw 47;

  }

  void operator delete[](void* p) {

    cout << "Deallocating a Dog" << endl;

    ::operator delete[](p);

  }

};

class UseResources {

  PWrap<Cat, 3> cats;

  PWrap<Dog> dog;

public:

  UseResources() {

    cout << "UseResources()" << endl;

  }

  ~UseResources() {

    cout << "~UseResources()" << endl;

  }

  void f() { cats[1].g(); }

};

int main() {

  try {

    UseResources ur;

  } catch(int) {

    cout << "inside handler" << endl;

  } catch(...) {

    cout << "inside catch(...)" << endl;

  }

} ///:~

The difference is the use of the template to wrap the pointers and make them into objects. The constructors for these objects are called before the body of the UseResources constructor, and any of these constructors that complete before an exception is thrown will have their associated destructors called during stack unwinding.

The PWrap template shows a more typical use of exceptions than you’ve seen so far: A nested class called RangeError is created to use in operator[ ] if its argument is out of range. Because operator[ ] returns a reference, it cannot return zero. (There are no null references.) This is a true exceptional condition—you don’t know what to do in the current context, and you can’t return an improbable value. In this example, RangeError is simple and assumes all the necessary information is in the class name, but you might also want to add a member that contains the value of the index, if that is useful.

Now the output is:.

Cat()

Cat()

Cat()

PWrap constructor

allocating a Dog

~Cat()

~Cat()

~Cat()

PWrap destructor

inside handler

Again, the storage allocation for Dog throws an exception, but this time the array of Cat objects is properly cleaned up, so there is no memory leak.

auto_ptr

Since dynamic memory is the most frequent resource used in a typical C++ program, the standard provides an RAII wrapper for pointers to heap memory that automatically frees the memory. The auto_ptr class template, defined in the <memory> header, has a constructor that takes a pointer to its generic type (whatever you use in your code). The auto_ptr class template also overloads the pointer operators * and -> to forward these operations to the original pointer the auto_ptr object is holding. You can, therefore, use the auto_ptr object as if it were a raw pointer. Here’s how it works:.

//: C01:Auto_ptr.cpp

// Illustrates the RAII nature of auto_ptr

#include <memory>

#include <iostream>

using namespace std;

class TraceHeap {

  int i;

public:

  static void* operator new(size_t siz) {

    void* p = ::operator new(siz);

    cout << "Allocating TraceHeap object on the heap "

         << "at address " << p << endl;

    return p;

  }

  static void operator delete(void* p) {

    cout << "Deleting TraceHeap object at address "

         << p << endl;

    ::operator delete(p);

  }

  TraceHeap(int i) : i(i) {}

  int getVal() const {

    return i;

  }

};

int main() {

  auto_ptr<TraceHeap> pMyObject(new TraceHeap(5));

  cout << pMyObject->getVal() << endl;  // prints 5

} ///:~

The TraceHeap class overloads the operator new and operator delete so you can see exactly what’s happening. Notice that, like any other class template, you specify the type you’re going to use in a template parameter. You don’t say TraceHeap*, however; auto_ptr already knows that it will be storing a pointer to your type. The second line of main( ) verifies that auto_ptr’s operator->( ) function applies the indirection to the original, underlying pointer. Most important, even though we didn’t explicitly delete the original pointer (in fact we can’t here, since we didn’t save its address in a variable anywhere), pMyObject’s destructor deletes the original pointer during stack unwinding, as the following output verifies:.

Allocating TraceHeap object on the heap at address 8930040

5

Deleting TraceHeap object at address 8930040

The auto_ptr class template is also handy for pointer data members. Since class objects contained by value are always destructed, auto_ptr members always delete the raw pointer they wrap when the containing object is destructed[5].

Function-level try blocks

Since constructors can routinely throw exceptions, you might want to handle exceptions that occur when an object’s member or base subobjects are initialized. To do this, you can place the initialization of such subobjects in a function-level try block. In a departure from the usual syntax, the try block for constructor initializers is the constructor body, and the associated catch block follows the body of the constructor, as in the following example.

//: C01:InitExcept.cpp

// Handles exceptions from subobjects

//{-bor}

#include <iostream>

using namespace std;

class Base {

  int i;

public:

  class BaseExcept {};

  Base(int i) : i(i) {

    throw BaseExcept();

  }

};

class Derived : public Base {

public:

  class DerivedExcept {

    const char* msg;

  public:

    DerivedExcept(const char* msg) : msg(msg) {}

    const char* what() const {

      return msg;

    }

  };

  Derived(int j)

  try

    : Base(j) {

    // Constructor body

    cout << "This won't print\n";

  }

  catch (BaseExcept&) {

    throw DerivedExcept("Base subobject threw");;

  }

};

int main() {

  try {

вернуться

5

 For more detail on auto_ptr, see Herb Sutter’s article entitled, "Using auto_ptr Effectively" in the October 1999 issue of the C/C++ Users Journal, pp. 63–67.