I l@ve RuBoard |
SolutionProblem: Arrays and auto_ptr Don't Mix
What is wrong with this code? Explain. Every delete must match the form of its new. If you use single-object new, you must use single-object delete; if you use the array form of new[], you must use the array form of delete[]. Doing otherwise yields undefined behavior, as illustrated in the following slightly modified code.
The problem with p2 is that auto_ptr is intended to contain only single objects, so it always calls delete, not delete[], on the pointer it owns. This means that using plain delete, p1 will be cleaned up correctly, but p2 will not. What will actually happen when you use the wrong form of delete depends on your compiler. The best you can expect is a resource leak. A more typical result is memory corruption soon followed by a core dump. To see this effect, try the following complete program on your favorite compiler:
This program will usually either crash or else output a running update of the number of leaked X objects. (For extra fun, try running a system-monitoring tool in another window that shows your system's total memory usage. It will help you to appreciate how bad the leak can be if the program doesn't crash right off.) Aside on a Non-problem: Zero-Length Arrays Are OkayWhat if f()'s parameter is zero (for example, in the call f<int>(0))? Then the second new turns into new T[0], and programmers often wonder: "Hmm, is this okay? Can we have a zero-length array?" The answer is yes. Zero-length arrays are perfectly okay, kosher, and fat-free. The result of new T[0] is just a pointer to an array with zero elements, and that pointer behaves just like any other result of new T[n] including the fact that you may not attempt to access more than n elements of the array. In this case, you may not attempt to access any elements at all because there aren't any. From 5.3.4 [expr.new], paragraph 7:
"If you can't do anything with zero-length arrays (other than remember their address)," you may wonder, "why should they be allowed?" One important reason is that it makes it easier to write code that does dynamic array allocation. For example, the function f() above would be needlessly more complex if it were forced to check the value of its n parameter before performing the new T[n] call. Of course, getting back to the main problem, just because zero-length arrays are legal doesn't mean we can get an auto_ptr to own one, any more than we can get an auto_ptr to own an array of any other length. We can't. The array-deletion problem remains.
There are several options (some better, some worse). Here are four: Option 1: Roll Your Own auto_arrayThis can be both easier and harder than it sounds. Option 1 (a): … By Deriving from auto_ptr (Score: 0 / 10)Bad idea. For example, you'll have a lot of fun reproducing all the ownership and helper-class semantics. This might be tried only by true gurus, but true gurus would never try it because there are easier ways. Advantages: Few. Disadvantages: Too many to count. Option 1 (b): … By Cloning auto_ptr Code (Score: 8 / 10)The idea here is to take the code from your library's implementation of auto_ptr, clone it (renaming it auto_array or something like that), and change the delete statements to delete[] statements. Advantages:
Disadvantage:
Option 2: Use the Adapter Pattern (Score: 7 / 10)This option came out of a discussion I had with C++ World attendee Henrik Nordberg, after one of my talks. Henrik's first reaction to the problem code was to wonder whether it would be easiest to write an adapter to make the standard auto_ptr work correctly, instead of rewriting auto_ptr or using something else. This idea has some real advantages and deserves analysis despite its few drawbacks. Here's the idea. Instead of writing
we write
where the ArrDelAdapter (array deletion adapter) has a constructor that takes a T* and a destructor that calls delete[] on that pointer:
Since there is only one ArrDelAdapter<T> object, the single-object delete statement in ~auto_ptr() is fine. Because ~ArrDelAdapter<T> correctly calls delete[] on the array, the original problem has been solved. Sure, this may not be the most elegant and beautiful approach in the world, but at least we didn't have to hand-code our own auto_array template! Advantage:
Disadvantages:
Having said all that, even though other alternatives turn out to be better in this particular case, I was very pleased to see people immediately think of using the Adapter pattern. Adapter is widely applicable and one of the core patterns every programmer should know. Guideline
Just a final note on Option 2: It's worth pointing out that writing
isn't much different from writing
Think about that for a moment. For example, ask yourself, "What, if anything, am I gaining by allocating the vector dynamically that I wouldn't have if I just wrote vector p2(n);?" Then see Option 4. Option 3: Replace auto_ptr with Hand-Coded Exception-Handling Logic (Score: 1 / 10 )Function f() uses auto_ptr for automatic cleanup and probably for exception safety. Instead, we could drop auto_ptr for the p2 array and hand-code our own exception-handling logic. That is, instead of writing:
we write something like:
Advantages:
Disadvantages:
Option 4: Use a vector Instead of an Array (Score: 9.5 / 10 )Most of the problems we've encountered have been due to the use of C-style arrays. If appropriate, and it's almost always appropriate, it would be better to use a vector instead of a C-style array. After all, a major reason why vector exists in the standard library is to provide a safer and easier-to-use replacement for C-style arrays! So instead of writing:
we write:
Advantages:
Disadvantages:
Note that passing or returning a vector by value is more work than passing or returning an auto_ptr. I consider this objection somewhat of a red herring, however, because it's an unfair comparison. If you want to get the same effect, you simply pass a pointer or reference to the vector. Guideline
|
I l@ve RuBoard |
No comments:
Post a Comment