C++ Memory Management: From Fear to Triumph, Part 3
Pages: 1, 2, 3, 4
Example 7. Handling memory via pointers: output
Deleting Derived Data, Deleting Base Data
The auto_ptr behaves like an ordinary pointer in many respects.
When an auto_ptr goes out of scope, however, it automatically
deletes the memory that it is holding — something that the ordinary
pointer does not do.
In addition to memory leaks, the auto_ptr also prevents
dangling references. When you assign one auto_ptr to another,
the auto_ptr on the right hand side actually becomes the
equivalent of a NULL pointer! The target of the assignment
(on the left hand side) now points to the memory that the right hand side
auto_ptr pointed to before. This is a powerful example of how
the semantics of an operation (in this case assignment) can be defined in
C++.
Recall our discussion in the second article about consistency of
ownership. The auto_ptr maintains such consistency.
First, it owns the memory that it points to. Second, the semantics of
assignment for auto_ptrs is transfer of ownership.
The target of the assignment becomes the new owner of the memory, while
the source turns into a NULL pointer, which owns nothing.
The two diagrams below contrast the assignment semantics of ordinary
pointers with those of the auto_ptr.

Figure 1. Assignment semantics of ordinary pointers

Figure 2. Assignment semantics of the auto_ptr
The auto_ptr is a very simple smart pointer, but it has
many uses (see the article Using
auto_ptr Effectively, for example). Its copy semantics, however, make
it unsuitable for some operations. Most notably, the
auto_ptr cannot be used in STL containers (the STL
is part of the C++ standard library, and is discussed next in this article). It is not necessary, however, to restrict yourself to just one kind of smart pointer. Boost.org, for example, provides free implementations of several smart pointers. Their shared_ptr is based on reference
counting (an elegant, efficient memory management technique that can
provide similar benefits to garbage collection) and is safe to use in
containers.
The smart pointer is a powerful idea for writing better C++
programs. The standard auto_ptr, the offerings at Boost.org, and many other implementations
provide you with lots of choices for various tasks. Using them as
examples, you might even write smart pointer classes yourself with
appropriately fine-tuned semantics for your application.
Be Aware of C++ Alternatives to Traditional C Methods
Because C++ is largely compatible with C, it is much easier to port C code into a C++ environment. It does not mean, however, that you should program in C++ as you would in C. In fact, the C++ standard library provides safe, simple solutions to many problems that are very troublesome in C.
For example, arrays and strings are two problematic issues that the C++ standard library solves very well. Here are two programs to illustrate this point. The first is written in the traditional C style, while the other one uses the new features of the C++ standard library.
Example 8. Array of strings, C style
//*** C STYLE ARRAY OF STRINGS ***
#include <stdio.h>
void print_strings(char** strings, unsigned num) {
for (int i = 0; i < num; ++i) {
printf("%s\n",strings[i]);
}
}
int main() {
char* strings[3] = {"One", "Two", "Three"};
print_strings(strings, 3);
}
//*** END: C STYLE ARRAY OF STRINGS ***
Example 9. Array of strings, C++ style
//*** C++ STYLE ARRAY OF STRINGS ***
#include <iostream>
#include <string>
#include <vector>
using namespace std;
void print_strings(const vector<string>& strings) {
for (int i=0; i < strings.size(); ++i) {
cout << strings[i] << endl;
}
}
int main() {
vector<string> strings(3);
strings[0] = "One"; strings[1] = "Two"; strings[2] = "Three";
print_strings(strings);
}
//*** END: C++ STYLE ARRAY OF STRINGS ***
Both examples produce the same output.
Example 10. Array of strings: output
One
Two
Three
The second example uses the string class from the standard C++
library. It also uses the vector template class, which implements
a resizable array. The vector template is part of the Standard Template Library (STL), a
powerful, elegant, and highly extensible set of algorithms and data structures.
(STL data structures such as vector are generally referred to as
containers because they are designed to hold other objects). The STL is
included in the C++ standard library. Performance of the first and second
example is similar, except in the initial step, where filling the
vector with string objects is significantly slower.
In many real-world situations, the initialization penalty for the C++ style
solution would be much smaller.
At first glance, the C++ example is more complex, but not overly so. Its
increased safety, however, is already apparent; vector knows its
own size. Thus, misstating the length of the array when calling
print_string — a disastrous error in the C style version
— is entirely avoided.
There's a lot more here that meets the eye, however. The true
difference between the C and C++ versions is in responsibility for memory
management. In the C++ example, vector and
string handle their own memory. Each string, for
instance, will automatically clean up its character buffer when the
vector is destroyed, even if all of the objects were
dynamically allocated. If the strings in the C example were dynamically
allocated, however, it would be necessary to provide extra code to delete
them. As a small illustration of the capabilities of the C++ programming
style, here is a slightly modified version of the array of strings
example.
Example 11. Array of strings, C++ style (version 2)
//*** C++ STYLE ARRAY OF STRINGS (VERSION 2) ***
#include <iostream>
#include <string>
#include <vector>
using namespace std;
void print_strings(const vector<string>& strings) {
for (int i=0; i < strings.size(); ++i) {
cout << strings[i] << endl;
}
}
int main() {
vector<string> strings(3);
strings[0] = "One"; strings[1] = "Two"; strings[2] = "Three";
print_strings(strings);
strings[0] += " and a";
strings[1] += " and a";
strings.resize(4);
strings[3] = "and One, Two, Three!";
cout << endl << "Encore!" << endl << endl;
print_strings(strings);
}
//*** END: C++ STYLE ARRAY OF STRINGS (VERSION 2) ***
Here is the output.
Example 12. Array of strings (version 2): output
One
Two
Three
Encore!
One and a
Two and a
Three
and One, Two, Three!
The examples shown here should give you an understanding of how C++
features can be applied to traditional C problems. Many variations are
possible. For example, a Boost.org
shared_ptr (covered previously) could be
used with vector. This would keep the automated resource
management, while allowing the vector to effectively contain
objects belonging to an inheritance hierarchy (note that you should never
use the standard auto_ptr inside vector or other
STL containers).
Doing things the C way still has its place. Working with legacy code, developing kernel-level software, or creating an embedded system often requires the older C style. Many decades and many platforms later, C is nowhere near obsolete. Nevertheless, the C++ alternatives are often the better choice, particularly for user-level code. In general C++ development, it is a good rule of thumb to favor the C++ solutions over traditional C approaches, resorting to the latter only when it is clearly necessary.