Archive for the ‘auto_ptr’ Category
Automatic Deallocation With AutoPtr
One of the major concepts in C++ that makes it so powerful, and therefore so difficult, is memory management. Even experienced programmers sometimes struggle with allocating and deallocating memory correctly and effectively. However, if done correctly (which is, of course, rather subjective), C++ will always be more efficient than any garbage collected language will ever be.
I recently ran into a memory management issue. I was employing a partial caching strategy, so in certain scenarios I wanted to return a pointer to memory stored in cache, and in other scenarios I needed to allocated new memory to return because the data did not exist in cache. This left me with two options: copy the data, or try to deal with deallocating the memory in the latter case. I chose to deal with deallocating the memory.
Initially I considered std::auto_ptr. No reason to reinvent the wheel for no good reason. However, std::auto_ptr did not provide facilities for specifying that the auto_ptr did not own the memory upon construction, which is something I needed to be able to specify. For this reason, and simply for the learning opportunity, I wrote my own version of an auto_ptr class with the functionality I needed (I realize I could have simply inherited std::auto_ptr to provide this functionality, but how fun would that have been). Here is the source code for this class:
template<typename _T>
class AutoPtr
{
public:
explicit AutoPtr(_T* ptr = 0, bool owned = true)
: _owned(owned),
_ptr(ptr) { }
AutoPtr(AutoPtr<_T>& other)
: _owned(other._owned),
_ptr(other.detach()) { }
template<typename _T1>
AutoPtr(AutoPtr<_T1>& other)
: _owned(other._owned),
_ptr(other.detach()) { }
virtual ~AutoPtr() {
if (_owned) delete _ptr;
}
_T* operator->() { return _ptr; }
_T& operator*() { return *_ptr; }
operator _T*() { return _ptr; }
const _T* operator->() const { return _ptr; }
const _T& operator*() const { return *_ptr; }
operator const _T*() const { return _ptr; }
AutoPtr& operator=(AutoPtr& lhs) {
reset(lhs.detach(), lhs._owned);
}
template<typename _T1>
AutoPtr& operator=(AutoPtr<_T1>& lhs) {
reset(lhs.detach(), lhs._owned);
}
_T* detach() {
_T* t = _ptr;
_ptr = 0;
_owned = false;
return t;
}
void reset(_T* ptr = 0, bool owned = true) {
if (_owned) delete _ptr;
_owned = owned;
_ptr = ptr;
}
protected:
bool _owned;
_T* _ptr;
protected:
struct ReferenceHelper
{
bool _owned;
_T* _ptr;
explicit ReferenceHelper(bool owned, _T* ptr)
: _owned(owned),
_ptr(ptr) { }
};
public:
AutoPtr(ReferenceHelper helper)
: _owned(helper._owned),
_ptr(helper._ptr) { }
AutoPtr& operator=(ReferenceHelper lhs) {
reset(lhs._ptr, lhs._owned);
}
operator ReferenceHelper() {
bool owned = _owned;
return ReferenceHelper(owned, detach());
}
};
Now, this type of thing has been done a thousand times in the past, but that’s OK. I’ll take the opportunity to walk through the code anyway. I’ll assume you have at least some basic template knowledge.
We start with a basic constructor and copy constructor, easy enough. Next is something slightly more interesting:
template<typename _T1>
AutoPtr(AutoPtr<_T1>& other);
This is a copy constructor that allows us to copy from an AutoPtr of a convertible type. i.e., An AutoPtr of a child class type being passed to the constructor of an AutoPtr of it’s parent class type. We then have some simple operators that give the class pointer semantics. These operators are what allow us to treat the AutoPtr class just like a real pointer. We then have 2 operator=’s, which are exactly like the 2 copy constructors.
Next we have:
_T* detach();
This method explicitly takes ownership of the managed memory from the AutoPtr instance. It is, of course, used in the copy constructors and operator=’s. And:
void reset(_T* ptr = 0, bool owned = true);
Which instructs the AutoPtr instance to manage new memory, destroying any previously managed memory first.
The ReferenceHelper type is also interesting. This simple struct gives AutoPtr reference semantics:
AutoPtr<MyClass> getMyClass()
{
return AutoPtr<MyClass>(new MyClass);
}
int main()
{
AutoPtr ptr = getMyClass();
}
Without this struct, we would not be able to properly manage the new’d instance of MyClass when returning from getMyClass. What actually happens here is:
AutoPtr is implicitly converted to ReferenceHelper
ReferenceHelper is implicitly converted to AutoPtr.
This allows us to correctly remember whether or not we own the allocated memory, while not destructing it when returning from getMyClass.
This class is especially useful in complex methods that “save” data, where errors can essentially happen at any point. If memory is allocated and managed with AutoPtr, we do not have to worry about cleaning up the allocated memory on various different code branches.