[EDIT: Thanks for your opinions. I think this topic is discussed enough. Since std::auto_ptr
is removed from C++17, I decided to drop the idea of inheriting from std::auto_ptr
to avoid compatibility problems in the future.]
I know that std::auto_ptr
is not a perfect class for automatic memory management because it doesn't support the deletion of arrays. However, I decided to create a better templated class (called owner_ptr
) which supports this by inheriting from std::auto_ptr
. It seems to be dangerous, but I think I've found a safe way to do this. Some advantages why I thought it was a good idea to inherit:
- I don't have to reimplement all functions of
std::auto_ptr
. - I can use the
std::auto_ptr_ref
feature to pass temporaryowner_ptr
objects by value (and pass ownership to the copy). - I know that I won't access
owner_ptr
objects through base class pointer, so the absence ofvirtual ~auto_ptr()
cannot cause problem.
(Here you can find the implementation of std::auto_ptr.)
I've created some test cases (see below) and the result was promising. I want to use the new class in my own project (just a project for myself). But I'm not sure, if there are any scenarios where it can fail. I would be grateful if you could take a look at my implementation and share your ideas/opinions about it. (E.g. if there is a function which I should override too or if it is a bad practice to do this in C++98/C++03.)
#include <memory>
#include <cstddef>
#include <iostream>
template <typename T>
class owner_ptr : public std::auto_ptr<T>
{
public:
explicit owner_ptr(T* p = NULL) : std::auto_ptr<T>(p) { std::cout << "owner_ptr<T>(T*) called." << std::endl; }
owner_ptr(owner_ptr<T>& rhs) : std::auto_ptr<T>(rhs.release()) { std::cout << "owner_ptr<T>(owner_ptr<T>&) called." << std::endl; }
owner_ptr(std::auto_ptr_ref<T> rhs) : std::auto_ptr<T>(rhs) { std::cout << "owner_ptr<T>(auto_ptr_ref<T>) called." << std::endl; }
};
/* "specialization" for arrays */
template <typename T>
class owner_ptr<T[]> : public std::auto_ptr<T>
{
public:
explicit owner_ptr(T* p = NULL) : std::auto_ptr<T>(p) { std::cout << "owner_ptr<T[]>(T*) called." << std::endl; }
owner_ptr(owner_ptr<T[]>& rhs) : std::auto_ptr<T>(rhs.release()) { std::cout << "owner_ptr<T[]>(owner_ptr<T[]>) called." << std::endl; }
owner_ptr(std::auto_ptr_ref<T> rhs) : std::auto_ptr<T>(rhs) { std::cout << "owner_ptr<T[]>(auto_ptr_ref<T>) called." << std::endl; }
void reset(T* ptr = NULL)
{
std::cout << "owner_ptr<T[]>::reset(T*) called." << std::endl;
if (std::auto_ptr<T>::get() != ptr)
{
this->~owner_ptr();
std::auto_ptr<T>::reset(ptr);
}
}
~owner_ptr()
{
std::cout << "owner_ptr<T[]>::~owner_ptr() called." << std::endl;
delete[] std::auto_ptr<T>::release();
}
};
I tried to test it as well as it was possible (sorry for the long code):
class A
{
public:
A() { std::cout << "A() called." << std::endl; }
virtual ~A() { std::cout << "~A() called." << std::endl; }
};
class B : public A
{
public:
B() { std::cout << "B() called." << std::endl; }
~B() { std::cout << "~B() called." << std::endl; }
};
void printTest()
{
static int test = 0;
std::cout << std::endl << "Test " << ++test << ":" << std::endl;
}
template <typename T>
void testFunction(owner_ptr<T> param)
{
std::cout << "testFunction<T>() called." << std::endl;
owner_ptr<T> op = param;
}
template <typename T>
void testFunction(owner_ptr<T[]> param)
{
std::cout << "testFunction<T[]>() called." << std::endl;
owner_ptr<T[]> op = param;
}
int main()
{
std::cout << "Testing owner_ptr<A>" << std::endl;
/* Test 1 */
/* Constructing with NULL-pointer */
printTest();
owner_ptr<A>();
/* Test 2 */
/* Constructing with valid pointer */
printTest();
owner_ptr<A>(new A);
/* Test 3 */
/* Testing "copy"-constructor */
printTest();
{
owner_ptr<A> op1(new A);
owner_ptr<A> op2(op1);
std::cout << "op1.get() result : " << op1.get() << std::endl;
}
/* Test 4 */
/* Testing owner_ptr<T>::operator= */
printTest();
{
owner_ptr<A> op1(new A);
owner_ptr<A> op2;
op2 = op1;
std::cout << "op1.get() result : " << op1.get() << std::endl;
}
/* Test 5 */
/* Passing owner_ptr<A> object by value */
printTest();
{
owner_ptr<A> op(new A);
testFunction(op);
}
/* Test 6 */
/* Constructing owner_ptr<A> with a temporary owner_ptr<A> object. */
/* = passing temporary object by non-const reference */
printTest();
{
owner_ptr<A>(owner_ptr<A>(new A));
}
/* Explicit constructor : owner_ptr<A>::owner_ptr<A>(A*) */
/* Implicit conversion : owner_ptr<A>::operator std::auto_ptr_ref<A>() (inherited from std::auto_ptr<A>) */
/* (Explicit) constructor : auto_ptr_ref<A>::auto_ptr_ref<A>(A*) */
/* Implicit constructor : owner_ptr<A>::owner_ptr<A>(std::auto_ptr_ref<A>) */
/* Test 7 */
/* Passing a temporary owner_ptr<A> object by value */
printTest();
{
testFunction(owner_ptr<A>(new A));
}
std::cout << std::endl << "Testing owner_ptr<A[]>" << std::endl;
/* Test 8 */
/* Constructing with NULL-pointer */
printTest();
owner_ptr<A[]>();
/* Test 9 */
/* Constructing with valid pointer */
printTest();
owner_ptr<A[]>(new A[3]);
/* Test 10 */
/* Testing "copy"-constructor */
printTest();
{
owner_ptr<A[]> op1(new A[3]);
owner_ptr<A[]> op2(op1);
std::cout << "op1.get() result : " << op1.get() << std::endl;
}
/* Test 11 */
/* Testing owner_ptr<A[]>::operator= */
printTest();
{
owner_ptr<A[]> op1(new A[3]);
owner_ptr<A[]> op2;
op2 = op1;
std::cout << "op1.get() result : " << op1.get() << std::endl;
}
/* Test 12 */
/* Passing owner_ptr<A[]> object by value */
printTest();
{
owner_ptr<A[]> op(new A[3]);
testFunction(op);
}
/* Test 13 */
/* Constructing owner_ptr<A[]> with a temporary owner_ptr<A[]> object */
/* = passing temporary object by non-const reference */
printTest();
{
owner_ptr<A[]>(owner_ptr<A[]>(new A[3]));
}
/* Test 14 */
/* Passing a temporary owner_ptr<A[]> by value */
printTest();
{
testFunction(owner_ptr<A[]>(new A[3]));
}
/* Test 15 */
/* Testing owner_ptr<A[]>::reset */
printTest();
{
owner_ptr<A[]>(new A[3]).reset(new A[2]);
}
/* Test 16 */
printTest();
{
owner_ptr<A> op(new B);
}
/* Testing compilation of owner_ptr<A> = owner_ptr<A[]> */
/* Result: compilation error */
/*
{
owner_ptr<A> op1;
owner_ptr<A[]> op2(new A[3]);
op1 = op2;
Result: compiler error.
}
*/
/* std::vector< owner_ptr<A> > container;
container.push_back(owner_ptr<A>(new B));
Won't compile due to missing
owner_ptr<A>(const owner_ptr<A>&) constructor.
R.I.P. */
return 0;
}
The result was the following:
Testing owner_ptr<A>
Test 1:
owner_ptr<T>(T*) called.
Test 2:
A() called.
owner_ptr<T>(T*) called.
~A() called.
Test 3:
A() called.
owner_ptr<T>(T*) called.
owner_ptr<T>(owner_ptr<T>&) called.
op1.get() result : 0
~A() called.
Test 4:
A() called.
owner_ptr<T>(T*) called.
owner_ptr<T>(T*) called.
op1.get() result : 0
~A() called.
Test 5:
A() called.
owner_ptr<T>(T*) called.
owner_ptr<T>(owner_ptr<T>&) called.
testFunction<T>() called.
owner_ptr<T>(owner_ptr<T>&) called.
~A() called.
Test 6:
A() called.
owner_ptr<T>(T*) called.
owner_ptr<T>(auto_ptr_ref<T>) called.
~A() called.
Test 7:
A() called.
owner_ptr<T>(T*) called.
owner_ptr<T>(auto_ptr_ref<T>) called.
testFunction<T>() called.
owner_ptr<T>(owner_ptr<T>&) called.
~A() called.
Testing owner_ptr<A[]>
Test 8:
owner_ptr<T[]>(T*) called.
owner_ptr<T[]>::~owner_ptr() called.
Test 9:
A() called.
A() called.
A() called.
owner_ptr<T[]>(T*) called.
owner_ptr<T[]>::~owner_ptr() called.
~A() called.
~A() called.
~A() called.
Test 10:
A() called.
A() called.
A() called.
owner_ptr<T[]>(T*) called.
owner_ptr<T[]>(owner_ptr<T[]>) called.
op1.get() result : 0
owner_ptr<T[]>::~owner_ptr() called.
~A() called.
~A() called.
~A() called.
owner_ptr<T[]>::~owner_ptr() called.
Test 11:
A() called.
A() called.
A() called.
owner_ptr<T[]>(T*) called.
owner_ptr<T[]>(T*) called.
op1.get() result : 0
owner_ptr<T[]>::~owner_ptr() called.
~A() called.
~A() called.
~A() called.
owner_ptr<T[]>::~owner_ptr() called.
Test 12:
A() called.
A() called.
A() called.
owner_ptr<T[]>(T*) called.
owner_ptr<T[]>(owner_ptr<T[]>) called.
testFunction<T[]>() called.
owner_ptr<T[]>(owner_ptr<T[]>) called.
owner_ptr<T[]>::~owner_ptr() called.
~A() called.
~A() called.
~A() called.
owner_ptr<T[]>::~owner_ptr() called.
owner_ptr<T[]>::~owner_ptr() called.
Test 13:
A() called.
A() called.
A() called.
owner_ptr<T[]>(T*) called.
owner_ptr<T[]>(auto_ptr_ref<T>) called.
owner_ptr<T[]>::~owner_ptr() called.
~A() called.
~A() called.
~A() called.
owner_ptr<T[]>::~owner_ptr() called.
Test 14:
A() called.
A() called.
A() called.
owner_ptr<T[]>(T*) called.
owner_ptr<T[]>(auto_ptr_ref<T>) called.
testFunction<T[]>() called.
owner_ptr<T[]>(owner_ptr<T[]>) called.
owner_ptr<T[]>::~owner_ptr() called.
~A() called.
~A() called.
~A() called.
owner_ptr<T[]>::~owner_ptr() called.
owner_ptr<T[]>::~owner_ptr() called.
Test 15:
A() called.
A() called.
A() called.
A() called.
A() called.
owner_ptr<T[]>(T*) called.
owner_ptr<T[]>::reset(T*) called.
owner_ptr<T[]>::~owner_ptr() called.
~A() called.
~A() called.
~A() called.
owner_ptr<T[]>::~owner_ptr() called.
~A() called.
~A() called.
Test 16:
A() called.
B() called.
owner_ptr<T>(T*) called.
~B() called.
~A() called.
It seems to be working properly. I think, the only problem is that I cannot create a heterogeneous collection like std::vector< owner_ptr<A> >
to store derived class instances. But it's not a real problem, because it can be easily solved by creating a similar class to std::shared_ptr
which has a real copy constructor.
If you have any idea how can I make this templated class better, before I use it in my own project, please share it. I'm using C++03.
If you think, it is a totally bad idea to inherit from std::auto_ptr
, please explain why.