For a couple of days, I have been trying to implement an own templated class called owner_ptr
(for my little project) whose instances has ownership over a dynamically allocated object (or array). I want this template to support the deletion of dynamically allocated arrays too and also make it possible to pass ownership between owner_ptr
objects. I've tried out many things and ideas (the last one was this) but each of them had some drawbacks.
The necessity of passing ownership between owner_ptr
objects causes that you must modify the pointer of the passed owner_ptr
object during copy-constructing or assignment (you have to set the pointer to NULL). Copy-constructor and assignment operator with const-reference is necessary to make this class work with std::vector
.
template <typename T>
class owner_ptr
{
/* Private members */
public:
owner_ptr(const owner_ptr<T>&);
owner_ptr& operator=(const owner_ptr<T>&);
/* Other public member functions */
}
But how can I modify the objects if I have const-reference to them? The problem can be solved if I store the encapsulated pointer as a mutable
member variable.
Here is my implementation:
template <typename T>
class owner_ptr
{
mutable T* p;
T* get_ownership() const
{
T* tmp = p;
p = NULL;
return tmp;
}
public:
explicit owner_ptr(T* ptr = NULL) : p(ptr) {}
owner_ptr(const owner_ptr<T>& orig) : p(orig.get_ownership()) { }
owner_ptr& operator=(const owner_ptr<T>& rhs)
{
if (this != &rhs)
{
this->pass(rhs.get_ownership());
}
return *this;
}
void pass(T* ptr = NULL)
{
if (p != ptr)
{
delete p;
p = ptr;
}
}
T* get() const { return p; }
T* operator->() const { return p; }
T& operator*() const { return *p; }
~owner_ptr()
{
this->pass();
}
};
And a specialization for arrays (almost the same):
template <typename T>
class owner_ptr<T[]>
{
mutable T* p;
T* get_ownership() const
{
T* tmp = p;
p = NULL;
return tmp;
}
public:
explicit owner_ptr(T* ptr = NULL) : p(ptr) {}
owner_ptr(const owner_ptr<T[]>& orig) : p(orig.get_ownership()) { }
owner_ptr& operator=(const owner_ptr<T[]>& rhs)
{
if (this != &rhs)
{
this->pass(rhs.get_ownership());
}
return *this;
}
void pass(T* ptr = NULL)
{
if (p != ptr)
{
delete[] p;
p = ptr;
}
}
T* get() const { return p; }
T* operator->() const { return p; }
T& operator*() const { return *p; }
T& operator[](int i) const { return p[i]; }
~owner_ptr()
{
this->pass();
}
};
I tested the code and it seems to be working. std::vector
also accepts this template, so I can use it for creating heterogeneous containers.
My questions:
- Is it a bad idea to use
mutable
to solve the ownership-passing problem? Are there any scenarios where it can cause undefined behaviour? - Is there anything that I implemented incorrectly in the template?
- Any affect on performance?
- Before C++11, why did we have only the silly
std::auto_ptr
which couldn't be used withstd::vector
?
NOTE: I'm doing this for learning purposes. This question is totally independent from the features and smart-pointers of C++11.
std::unique_ptr
without move semantics. That's impossible.std::auto_ptr
can't be used withstd::vector
, because the copy c'tor passes ownership.std::unique_ptr
fixes this by not being copyable, but only movable. Now, you can emulate move semantics in C++03, but third party code won't be able to use your emulation. So, you still can't use your class together withstd::vector
. \$\endgroup\$std::auto_ptr
: because it's a hard problem, and required the invention of move semantics, which in turn caused the invention of rvalue references and substantial changes to the language to support this part of the Standard Library. It's not the kind of thing that can be quietly added as an afterthought. \$\endgroup\$std::vector
? In fact, I was able topush_back
owner_ptr
objects into the container. Also, what is considered to be third party code? I'm using only the C++03 standard library. I've created this template for personal use only. \$\endgroup\$auto_ptr
to the standard library before move semantics and rvalue reference were invented. If I was able to create a better one with C++03 tools... Maybe it's an ugly solution, but it would have been better than the almost uselessauto_ptr
before C++11. \$\endgroup\$auto_ptr
invector
but it has severe limitations that are really easy to violate. The real problem is that violating these constraints did not cause a compiler error and resulted in some very strange and unexpected consequence. \$\endgroup\$