oreilly.comSafari Books Online.Conferences.


C++ Memory Management: From Fear to Triumph, Part 3
Pages: 1, 2, 3, 4

The Training Wheels Class

Here is a useful example of software engineering in action. Ask yourself "What if someone tried to copy an object of my class? What if they attempted to assign one object of my class to another?" As shown in the first article, the default copy constructor and assignment operator that C++ supplies are often dangerous. You may not have the opportunity to write your own versions of these methods right away. You may even see no need for anyone to copy or assign objects of your class and so wish to disallow these operations altogether. Clearly, safer default behavior would be very useful. This is the purpose of the Training Wheels class.

Example 3. The Training Wheels class

//Pattern your classes on this class, at least
//during initial development.  It is safer than
//the defaults that the C++ compiler generates.
class TWC {

  //The default constructor, just so that the examples
  //work.  You'll define your own constructors, which
  //may or may not include the default constructor.
  //You may want to include a virtual destructor,
  //but if derived classes will will define
  //actual copy constructors and assignment
  //operators, there is no way to copy the base
  //class, so be careful.

  //The copy constructor and assignment operator 
  //are disabled.  Clients of this class that attempt 
  //to use them will cause errors at compile time.
  TWC(const TWC&);
  TWC operator=(const TWC&);


//The do-nothing default constructor.
TWC::TWC() {}

The key feature of the Training Wheels class is that the copy constructor and assignment operator are declared private. Thus, when someone tries to copy an object of your class or assign one object of your class to another, the compiler generates an error message. The copy constructor and assignment operator being private are not accessible. For example, here is what happens when a program tries to assign one instance of the Training Wheels class to another (the exact wording of the error messages is compiler dependent).

Example 4. The Training Wheels class: attempted assignment

$ g++3 twct.cpp
twc.ex: In function `int main()':
twc.ex:24: `TWC TWC::operator=(const TWC&)' is private
twct.cpp:11: within this context

Related Reading

C++ In a Nutshell
A Desktop Quick Reference
By Ray Lischner

Forgetting to define the copy constructor and the assignment operator is a common error. The Training Wheels class does not prevent you from making it. Users of your class are unlikely to be happy when their code does not compile due to your mistakes. A compiler error, however, is a far more preferable outcome than the disaster of a dangling reference. The Training Wheels class is not a recipe (even in a very local, limited sense) for error-free software; that would be impossible. Instead, the class is an honest recognition of the possibility of errors together with a conscious, calculated attempt to contain the damage. To use the Training Wheels class as the starting point for your own classes constitutes a concrete, practical act of software engineering.

The true engineer knows that the real world is full of uncertainty and error, that her calculations no matter how meticulously done can never capture this world in all of its complexity. The true engineer asks, for example, "what would happen if my estimate of the maximum wind loading on this building is off by a fifty percent, or by a factor of two, or a factor of five?". She accepts the reality of an uncertain world, indeed of her own human fallibility in that world, and it is only through this acceptance that she may achieve a good design. Of course, it is always possible to go too far with such reasoning; the result is commonly referred to as "over-built" or "over-engineered". Nevertheless, this kind of thinking is central to a practical design. We refer to it as adding a safety margin.

In considering the Training Wheels class, we acknowledge the fact that software engineers, too, live in a world of uncertainty and error. The very human act of creating software makes it so. Thus, the true software engineer asks, for example, "what would happen if someone tried to copy an object of my class, or to assign to an object of my class," accepting all the while that mistakes are possible. Using the Training Wheels class instead of the C++ default is an engineer's way of dealing with the very real possibility of these mistakes. It is an example of a safety margin in software.

Objects that Act Like Pointers

In C++ you can create classes that are almost an extension of the language. We have already seen an example of this with the assignment operator. The syntax of assigning objects to one another is exactly the same as that for integers, doubles, or any other built-in C++ type. When you define your own assignment operator, however, you are effectively determining the semantics of assignment for objects of your class. The syntax (roughly, how something is expressed) remains the same and is fixed by C++. The semantics (roughly, what something means) are up to you.

The ability to determine the meaning of various operations as they apply to your classes can be brought to bear on a very problematic feature that C++ has inherited from C: the pointer. As you have already seen in the first article, manipulating memory via pointers can be very dangerous because memory leaks and dangling references occur quite readily. While C++ is far less dependent on pointers than C, they are still a highly useful tool in many situations. It is not desirable to give up pointers altogether: what we really want is a safe pointer.

How is it possible to achieve a safe pointer without rewriting the language? In C++, we simply create a class that behaves like a pointer. We define the pointer dereference operation for our class, and make everything else roughly consistent with pointer behavior. Most of the code using our pointer-like object looks exactly the same as if we were using ordinary pointers. Our "pointers", however, can be made to do a great deal more, such as preventing dangling references. Compared to ordinary, dumb pointers, these pointer-like objects seem very intelligent. Hence, their common name: smart pointers.

Probably the first smart pointer that you will encounter is the standard C++ library auto_ptr. Here are two sample pieces of code. Both are correct, but the first uses ordinary pointers, while the second takes advantage of the standard auto_ptr.

Example 5. Handling memory via an ordinary pointer

Base* obj_p = 0;
try {
  obj_p = new Derived;


//Must catch everything, so that "obj_p"
//does not leak.
catch(...) {
  delete obj_p; //Cleanup.
  throw;        //Rethrow.

Example 6. Handling memory via the standard auto_ptr

//Can assign a derived object to a base 
//pointer just like with ordinary pointers.
auto_ptr<Base> obj_sp (new Derived);

//The auto_ptr "obj_sp" will free the 
//object that it points to (if any).

It is clear that the second example is much less prone to errors. The output is the same in both cases (assuming something eventually catches the exception without rethrowing it).

Pages: 1, 2, 3, 4

Next Pagearrow

Linux Online Certification

Linux/Unix System Administration Certificate Series
Linux/Unix System Administration Certificate Series — This course series targets both beginning and intermediate Linux/Unix users who want to acquire advanced system administration skills, and to back those skills up with a Certificate from the University of Illinois Office of Continuing Education.

Enroll today!

Linux Resources
  • Linux Online
  • The Linux FAQ
  • Linux Kernel Archives
  • Kernel Traffic

  • Sponsored by: