C++ Memory Management: From Fear to Triumph, Part 3
by George Belotsky08/07/2003
- Techniques for C++ Memory Management.
- Memory Allocation Considered Harmful
- Series Conclusion: Every C++ Program Needs a Memory Management Design
- Further Reading
Techniques for C++ Memory Management.
The first article in this series covered common C++ memory errors, while the second described the general nature of the C++ memory management mechanism. Now it's time to present a list of simple, powerful techniques that you can use to deal with memory in your C++ programs. It is best not to read this article in isolation; the previous articles will help you use the techniques presented here much more effectively.
An Ownership-Consistent SimpleString -- the Classic Example Continued
The first article
of this series demonstrated a classic
dangling reference by defining a class called
SimpleString. Subsequent
discussion in the second article showed
that the problem in SimpleString is actually caused by
divergent assumptions about memory ownership among the methods of that
class. (The concept of memory ownership is critical: you should read about it
now if you have not done so already.) In the original
SimpleString, the destructor is written as if
SimpleString objects own the memory used for their character
buffers. The default copy constructor and assignment operator, however,
act as though something else were responsible for that memory.
One way to make all of SimpleString behave consistently is
to write our own copy constructor and assignment operator for the class,
instead of relying on the default versions of these methods supplied by
the C++ compiler. The following two examples illustrate this approach to
fixing SimpleString.
|
Related Reading Secure Programming Cookbook for C and C++ |
Example 1. simplestring.h
//*** SIMPLESTRING DECLARATION ***
#ifndef EXAMPLE_SIMPLE_STRING
#define EXAMPLE_SIMPLE_STRING
class SimpleString {
public:
explicit SimpleString(char* data = ""); //Use 'explicit' keyword to disable
//automatic type conversions --
//generally a good idea.
//Copy constructor and assignment operator.
SimpleString(const SimpleString& original);
SimpleString& operator=(const SimpleString& right_hand_side);
virtual ~SimpleString(); //Virtual destructor, in case someone inherits
//from this class.
virtual const char* to_cstr() const; //Get a read-only C string.
//Many other methods are needed to create a complete string class.
//This example implements only a tiny subset of these, in order
//to keep the discussion focused.
//N.B. no 'inline' methods -- add inlining later, if needed for
//optimization.
private:
char* data_p_; //Distinguish private class members: a trailing underscore
//in the name is one common method.
};
#endif
//*** END: SIMPLESTRING DECLARATION ***
//*** SIMPLESTRING IMPLEMENTATION ***
#include <cstring>
#include "simplestring.h"
using namespace std;
//Constructor
SimpleString::SimpleString(char* data_p) :
data_p_(new char[strlen(data_p)+1]) {
strcpy(data_p_,data_p);
}
//Copy constructor.
SimpleString::SimpleString(const SimpleString& original) :
data_p_(new char[strlen(original.data_p_)+1]) {
strcpy(data_p_,original.data_p_);
}
//Assignment operator.
SimpleString&
SimpleString::operator=(const SimpleString& right_hand_side) {
//It is possible for the caller to request assignment to self
//(i.e. "a = a;"). Do nothing in this case, or a serious
//error will result.
if (this != &right_hand_side) {
//Allocate a new buffer first. If this fails (i.e. throws
//an exception), everything is still consistent.
char* data_p = new char[strlen(right_hand_side.data_p_)+1];
//Now, delete the old buffer, and start using the new one.
delete [] data_p_; //(1)
data_p_ = data_p; //(2)
//Copy the data over from the right hand side. We checked
//before that this is not self assignment, so we are safe.
//Otherwise, we would have already destroyed this data
//in statements (1) and (2)!
strcpy(data_p_,right_hand_side.data_p_);
}
//This allows assignments to be chained (i.e. "a = b = c = d;").
return *this;
}
//Destructor
SimpleString::~SimpleString() {
//N.B. Use of 'delete []' corresponds to previous use of 'new []'.
// Using just 'delete' here would be a disaster.
delete [] data_p_;
}
//Returns a read-only C string representation.
const char* SimpleString::to_cstr() const {
return data_p_;
}
//*** END: SIMPLESTRING IMPLEMENTATION ***
As you can see, providing your own copy constructor and assignment operator is quite a bit of work. Fortunately, there are alternatives that will often make the defaults work as intended. The C++ standard library offers solutions for many such situations, for example. Smart pointers are another possibility and so is the Training Wheels class. Still, there will always be cases where a good design requires custom constructors and assignment operators.
