Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
104 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

3. Manage your class data to avoid resource leaks and crashes

There are other resources besides pointers that you might have to manage in your program: file handles, sockets, semaphores, etc. And sometimes you will manage raw pointers. The way to effectively manage resources in C++ is to wrap them in a class that performs their allocation and initialization in the constructor and their deallocation in the destructor:

class ResourceHog {
  SomeObject * object_;
  FILE * file_;
public:
  ResourceHog()
  {
    object_ = new SomeObject();
    file_ = fopen("myfile", "r");
  }
  ~ResourceHog()
  {
    delete object_;
    fclose(file_);
  }
};

The constructor allocates and opens resources, the destructor closes and deallocates them. But you're not done yet. When your objects are copied or assigned, their copy constructors and assignment operators are called.

"But I didn't write a copy constructor or assignment operator!", you protest.

Perhaps you didn't, but your compiler did for you, and their default behavior is to do a bitwise copy of your member data -- probably not what you want if your object manages pointers or other resources. Consider the following:

class String {
  char * data_;
public:
  // Member functions
};

String s1("hello, memory problems");
String s2 = s1;  // String copy constructor called
String s3;

s3 = s1;   // String assignment operator called

If you didn't write a copy constructor or assignment operator for String to allocate space for the data_ member and copy its contents, then your compiler generated one that merely copies the data_ *pointer*. Now you have three String objects, each with a copy of the same pointer but not with a copy of the data which is pointed to by the pointer.

When the destructor is called for the first of these String objects that goes out of scope, the pointer is deleted and the memory pointed to is reclaimed by the system. Recall that the other two copies of the pointer still point to that reclaimed data -- i.e., they are "dangling pointers" -- so when one of their destructors is called, an access violation occurs and your program crashes. The access violation occurs since an attempt to access reclaimed memory is made.

One way to fix this problem is to not let client code copy or assign objects of your class. The trick here is to *declare* the copy constructor and assignment operator, but not implement them:

class String {
  char * data_;
  // Copy c'tor with no implementation
  String(const String &);
  
  // Assignment op with no implementation
  String & operator=(const String &);
public:
  // Member functions
};

Now code that tries to copy or assign objects of your class will not compile. That's because it can't access your private functions. Friend and member functions that try the same will compile, but the linker will stop them since these functions are not defined.

Not allowing copying and assignment is, in fact, my default MO when designing a new class. Rarely do you need multiple copies of objects floating around your program. If you do need a copy constructor and assignment operator, they are pretty easy to code up. You're just creating a new object from an existing one:

Matrix::Matrix(const Matrix & m)
{
  data_ = new float[16];
  memcpy(data_, m.data_, 16 * sizeof(float));
}

Matrix & Matrix::operator=(const Matrix & m)
{
  if (this != &m) {  // Check for self-assignment
    float * newData = new float[16];
    delete [] data_;
    data_ = newData;
    memcpy(data_, m.data_, 16 * sizeof(float));
  }

  return *this;
}

A good rule of thumb is that if you need a destructor, then you also need a copy constructor and assignment operator (or you need to prevent other code from calling them as shown above).

4. Use virtual destructors in class hierarchies

Compile and run the following program and observe what happens when the pointer to Base is deleted:

#include <iostream>

using namespace std;

class Base {
  // Base private data
  // Base private member functions
public:
  ~Base() { cout << "~Base()" << endl; }
  // Base public member functions
};

class Derived : public Base {
  // Derived private data
  // Derived private member functions
public:
  ~Derived() { cout << "~Derived()" << endl; }
  // Derived public member functions
};

int main()
{
  Base * bp = new Derived();

  delete bp;
}

On my system, the output looks like this:

$ myprogram
~Base()
$

The Derived destructor wasn't called! This means that the Derived portion of the object is still floating around and taking up memory, and you have no way of getting it back. That's called a memory leak. It's also a problem in the C++ world known as the "slicing" of an object. Fortunately, it is easily prevented by declaring your base class destructors as virtual:

virtual ~Base() { cout << "~Base()" << endl; }

Now the destructor is called polymorphically through the pointer to Base. What this means is that the object is destructed in reverse order in which it was constructed: the Derived destructor is called, then the Base destructor, which is what you want.

Making ~Base() virtual in the above example, you'll see that the object is now properly destructed from the top down:

$ myprogram
~Derived()
~Base()
$

5. Use smart pointers instead of raw pointers

In addition to wrapping pointers in classes as shown above, "smart" or "managed" pointers can be a great help in managing memory. The standard library offers std::auto_ptr, which is designed to prevent memory leaks in the face of thrown exceptions. As shown here, raw pointers are prone to leaks when exceptions are thrown before they are cleaned up:

Foo * fp = new Foo();   
someFunctionThatMightThrow();
delete fp;

If the function throws an exception before you delete fp, you've leaked memory. You can fix this with std::auto_ptr, whose destructor will clean up the pointer it owns:

std::auto_ptr<Foo> fp(new Foo());
// if it throws, ~auto_ptr() cleans up the Foo *
someFunctionThatMightThrow();

auto_ptr has limitations, however. Its copying semantics transfer ownership of the raw pointer. Therefore, if you pass an auto_ptr *by value* to a function, the temporary copy in the function gains ownership of the raw pointer. When the function returns, the temporary copy is destroyed along with the raw pointer -- almost certainly what you don't want! If you want to avoid this kind of behavior, use a smart pointer that implements "reference counting", like Boost's shared_ptr:

boost::shared_ptr<Foo> fp(new Foo());

You can now pass fp around and not worry about ownership issues. shared_ptr will managed the references to it and delete the raw pointer when the last reference goes out of scope. Smart pointers are often used in containers:

std::vector<boost::shared_ptr<Monster> > monsters;

boost::shared_ptr<Monster> imp(new Imp());
monsters.push_back(imp);

boost::shared_ptr<Monster> troll(new Troll());
monsters.push_back(troll);

boost::shared_ptr<Monster> ogre(new Ogre());
monsters.push_back(ogre);

Contrast the above with the old fashioned C solution using a variable sized array holding raw pointers. I get chills up and down my spine just thinking about it. std::vector and boost::shared_ptr handle all of the memory management for you as well as provide access, iteration, searching, etc. You are now free to work on your application logic and stop chasing pointer errors.

For more information on boost::shared_ptr and the entire Boost library, visit http://boost.org.

6. Summary

To recap, the following simple rules can help you avoid memory problems in your C++ programs:

  1. Use std::string instead of char * or char []
  2. Use standard containers instead of homegrown containers
  3. Manage your class data to avoid resource leaks and crashes
  4. Use virtual destructors in class hierarchies
  5. Use smart pointers instead of raw pointers

Finally, a word about performance. While these idioms can help you write safer and more robust programs, there is a cost associated with them, both in code size and performance overhead. You may not be checking the bounds of std::vector or std::string, but the code inside them certainly is, and this costs CPU cycles. So, you might conclude that the moral of the story is that while this stuff is nice in theory, your application needs to scream, so in reality you'll just continue using good old fashion pointers, arrays, and homegrown containers. Right?

Wrong. As a wise programmer once said, premature optimization is the root of all evil. Do things the safe way first. If your program is slower than you need, profile it in a profiler and find the bottlenecks. Then and only then hand tune the offending code. Chances are -- especially in graphics applications and games -- the bottlenecks in your code won't be in the C++ standard library, but rather in your algorithms.

I hope you found this article helpful. You can find more articles, software, and other downloads at http://davemikesell.com.

7. References

  1. Josuttis, Nicolai, The C++ Standard Library, Addison Wesley
  2. Meyers, Scott, Effective C++, Addison Wesley
  3. Meyers, Scott, More Effective C++, Addison Wesley
  4. Cline, M., Lomow, G., Girou, M., C++ FAQs', Addison Wesley



Contents
  Introduction
  Page 2

  Printable version
  Discuss this article