Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Chapter 25

SimpleTemplate<string> stringWrapper(str); str += “!”;

cout << “wrapper value is “ << stringWrapper.get() << endl;

}

There Must Be a Better Way

As you read this paragraph, thousands of C++ programmers throughout the world are solving problems that have already been solved. Someone in a cubicle in San Jose is writing a smart pointer implementation from scratch that uses reference counting. A young programmer on a Mediterranean island is designing a class hierarchy that could benefit immensely from the use of mix-in classes.

As a Professional C++ programmer, you need to spend less of your time reinventing the wheel, and more of your time adapting reusable concepts in new ways. The following techniques are generalpurpose approaches that you can apply directly to your own programs or customize for your needs.

Smart Pointers with Reference Counting

Chapters 4 and 13 introduced the notion of a smart pointer: a method for wrapping dynamically allocated memory in a safe stack-based variable. Chapter 16 showed an implementation of a smart pointer using a template class. The following technique enhances the example from Chapter 16 by including reference counting.

The Need for Reference Counting

As a general concept, reference counting is the technique for keeping track of the number of instances of a class or particular object that are in use. A reference-counting smart pointer is one that keeps track of how many smart pointers have been built to refer to a single real pointer. This way, smart pointers can avoid double deletion.

The double deletion problem is easy to provoke with non-reference-counting smart pointers. Consider the following class, Nothing, which simply prints out messages when an object is created or destroyed.

class Nothing

{

public:

Nothing() { cout << “Nothing::Nothing()” << endl; } ~Nothing() { cout << “Nothing::~Nothing()” << endl; }

};

If you were to create two standard C++ auto_ptrs and have them both refer to the same Nothing object, both smart pointers would attempt to delete the same object when they go out of scope:

void doubleDelete()

{

Nothing* myNothing = new Nothing();

auto_ptr<Nothing*> autoPtr1(myNothing); auto_ptr<Nothing*> autoPtr2(myNothing);

}

736

Incorporating Techniques and Frameworks

The output of the previous function would be:

Nothing::Nothing()

Nothing::~Nothing()

Nothing::~Nothing()

Yikes! One call to the constructor and two calls to the destructor? And this from a class that’s supposed to make pointers safe?

If you only use smart pointers for simple cases, such as allocating memory that is only used within a function, this issue will not be a problem. However, if your program stores several smart pointers in a data structure or otherwise complicates the use of smart pointers by copying them, assigning them, or passing them as arguments to functions, adding another level of safety is essential.

A reference-counting smart pointer is safer than the built-in auto_ptr because it keeps track of the number of references to a pointer and deletes the memory only when it is no longer in use.

The SuperSmartPointer

The approach for SuperSmartPointer, a reference-counting smart pointer implementation is to keep a static map for reference counts. Each key in the map is the memory address of a traditional pointer that is referred to by one or more SuperSmartPointers. The corresponding value is the number of SuperSmartPointers that refer to that object.

The implementation of SuperSmartPointer that follows is based on the smart pointer code shown in Chapter 16. You may want to review that code before continuing. The major changes occur when a new pointer is set (through the single argument constructor, the copy constructor, or operator=) and when a SuperSmartPointer is finished with an underlying pointer (upon destruction or reassignment with operator=).

On initialization of a new pointer, the initPointer() method checks the static map to see if the pointer is already contained by an existing SuperSmartPointer. If it is not, the count is initialized to 1. If it is already in the map, the count is bumped up. When the pointer is reassigned or the containing SuperSmartPointer is destroyed, the finalizePointer() method is called. The method begins by printing an error if the pointer is not found in the map. If the pointer is found, its count is decremented by one. If this brings the count down to zero, the underlying pointer can be safely released. At this time, the key/value pair is explicitly removed from the map to keep the map size down.

#include <map> #include <iostream>

template <typename T> class SuperSmartPointer

{

public:

explicit SuperSmartPointer(T* inPtr); ~SuperSmartPointer();

SuperSmartPointer(const SuperSmartPointer<T>& src);

SuperSmartPointer<T>& operator=(const SuperSmartPointer<T>& rhs);

737

Chapter 25

const T& operator*() const; const T* operator->() const; T& operator*();

T* operator->();

operator void*() const { return mPtr; }

protected: T* mPtr;

static std::map<T*, int> sRefCountMap;

void finalizePointer(); void initPointer(T* inPtr);

};

template <typename T>

std::map<T*, int>SuperSmartPointer<T>::sRefCountMap;

template <typename T> SuperSmartPointer<T>::SuperSmartPointer(T* inPtr)

{

initPointer(inPtr);

}

template <typename T>

SuperSmartPointer<T>::SuperSmartPointer(const SuperSmartPointer<T>& src)

{

initPointer(src.mPtr);

}

template <typename T> SuperSmartPointer<T>&

SuperSmartPointer<T>::operator=(const SuperSmartPointer<T>& rhs)

{

if (this == &rhs) { return (*this);

}

finalizePointer();

initPointer(rhs.mPtr);

return (*this);

}

template <typename T> SuperSmartPointer<T>::~SuperSmartPointer()

{

finalizePointer();

}

template<typename T>

void SuperSmartPointer<T>::initPointer(T* inPtr)

{

mPtr = inPtr;

if (sRefCountMap.find(mPtr) == sRefCountMap.end()) {

738

Incorporating Techniques and Frameworks

sRefCountMap[mPtr] = 1; } else {

sRefCountMap[mPtr]++;

}

}

template<typename T>

void SuperSmartPointer<T>::finalizePointer()

{

if (sRefCountMap.find(mPtr) == sRefCountMap.end()) { std::cerr << “ERROR: Missing entry in map!” << std::endl; return;

}

sRefCountMap[mPtr]--;

if (sRefCountMap[mPtr] == 0) {

// No more references to this object--delete it and remove from map sRefCountMap.erase(mPtr);

delete mPtr;

}

}

template <typename T>

const T* SuperSmartPointer<T>::operator->() const

{

return (mPtr);

}

template <typename T>

const T& SuperSmartPointer<T>::operator*() const

{

return (*mPtr);

}

template <typename T>

T* SuperSmartPointer<T>::operator->()

{

return (mPtr);

}

template <typename T>

T& SuperSmartPointer<T>::operator*()

{

return (*mPtr);

}

Unit Testing the SuperSmartPointer

The Nothing class defined above can be employed for a simple unit test for SuperSmartPointer. One modification is needed to determine if the test passed or failed. Two static members are added to the Nothing class, which track the number of allocations and the number of deletions. The constructor and destructor modify these values instead of printing a message. If the SuperSmartPointer works, the numbers should always be equivalent when the program terminates.

739

Chapter 25

class Nothing

{

public:

Nothing() { sNumAllocations++; } ~Nothing() { sNumDeletions++; }

static int sNumAllocations; static int sNumDeletions;

};

int Nothing::sNumAllocations = 0; int Nothing::sNumDeletions = 0;

Following is the actual test. Note that an extra set of curly braces is used to keep the SuperSmartPointers in their own scope so that they are both allocated and destroyed inside of the function.

void testSuperSmartPointer()

{

Nothing* myNothing = new Nothing();

{

SuperSmartPointer<Nothing> ptr1(myNothing); SuperSmartPointer<Nothing> ptr2(myNothing);

}

if (Nothing::sNumAllocations != Nothing::sNumDeletions) { std::cout << “TEST FAILED: “ << Nothing::sNumAllocations <<

allocations and “ << Nothing::sNumDeletions <<

deletions” << std::endl;

} else {

std::cout << “TEST PASSED” << std::endl;

}

}

A successful execution of this test program will result in the following output:

TEST PASSED

You should also write additional tests for the SuperSmartPointer class. For example, you should test the copy construction and operator= functionality.

Enhancing This Implementation

The static reference count map provides the SuperSmartPointer with an additional layer of safety over built-in C++ smart pointers. However, the new implementation is not completely free of problems.

Recall that templates exist on a per-type basis. In other words, if you have some SuperSmartPointers that store pointers to integers and others that store pointers to characters, there are actually two classes generated at compile time: SuperSmartPointer<int> and SuperSmartPointer<char>. Because the reference count map is stored statically within the class, two maps will be generated. In most cases, this won’t cause a problem, but you could cast a char* to an int* resulting in two SuperSmartPointers of

740