Try to avoid defining a class that contains a pointer to an object allocated from the heap. For example, consider the situation where a car contains an engine. There are two choices: the preferred way would be for the engine object to be physically embedded inside the car object, and the undesirable way would be for the car object to contain a pointer to the engine object, where the car allocates the engine object from the heap. Here is a sample Engine class: #include <iostream> using namespace std; class Engine { public: Engine(); virtual void start(); }; Engine::Engine() { cout << "Engine constructor\n"; } void Engine::start() { cout << "Engine::start()\n"; } The car class shown in the following code, class Car, uses the preferred approach: each Car object physically contains its Engine object. Compared to using a pointer to an Engine allocated from the heap, the technique shown in class Car is easier, safer, and faster, and it uses less memory. class Car { public: Car(); virtual void startEngine(); protected: Engine e_; <-- 1 }; Car::Car() : e_ () <-- 2 { // Intentionally left blank } void Car::startEngine() { e_.start(); <-- 3 }
Although this is the preferred approach, sometimes it is necessary, or perhaps expedient, to allocate the inner object from the heap and have the outer object contain a pointer to the inner object. When this happens, an auto_ptr should be used: #include <memory> <-- 1 using namespace std; typedef auto_ptr<Engine> EnginePtr; class Car { public: Car(); virtual void startEngine(); virtual ~Car(); Car(const Car& c); <-- 2 Car& operator= (const Car& c); <-- 3 protected: EnginePtr p_; <-- 4 }; Car::Car() : p_ (new Engine()) <-- 5 { // Intentionally left blank } void Car::startEngine() { p_->start(); <-- 6 }
Logically this second example is still a contains or has-a relationship, but physically the implementation is somewhat different. Note the three extra member functions that must be declared in the second version of class Car. These extra member functions are needed because an auto_ptr is used to hold the car's Engine object. The most important message here is that it is much less dangerous to use auto_ptr than to use a raw hardware pointer, such as Car*. Thus the following technique should not be used. class Car { public: Car(); virtual void startEngine(); virtual ~Car(); Car(const Car& c); <-- 1 Car& operator= (const Car& c); <-- 2 protected: Engine* p_; <-- 3 };
The particular dangers of using raw hardware pointers are outlined later in the book, but for now simply use an auto_ptr as shown in the second example. |