- Why is
auto_ptr
deprecated? - Why
unique_ptr
finally works good? - How to use arrays with
unique_ptr?
- Why create
shared_ptr
withmake_shared?
- How to use arrays with
shared_ptr?
- How to pass smart pointers to functions?
- How to cast smart pointers?
- Wrap up
Updated on 27th August 2014: Added section about pointer casting. Plus a clean up.
Some predefines
Let us take a simpleTest
class with one member field to present further concepts:class Test
{
public:
Test():m_value(0) { std::cout << "Test::Test" << std::endl; }
~Test() { std::cout << "Test::~Test destructor" << std::endl; }
int m_value;
};
typedef std::auto_ptr<Test> TestAutoPtr;
typedef std::unique_ptr<Test> TestUniquePtr;
typedef std::shared_ptr<Test> TestSharedPtr;
Why is auto_ptr
deprecated?
auto_ptr
was one of the first type of smart pointers introduced in C++ (in C++98 to be more precise). It was designed to serve as a simple unique pointer (only one owner, without any reference counter), but people tried to use this also in a form of shared pointer. None of those functionalities were satisfied by auto_ptr
's implementation! Quick example below:void doSomething(TestAutoPtr myPtr) {
myPtr->m_value = 11;
}
void AutoPtrTest() {
TestAutoPtr myTest(new Test());
doSomething(myTest);
myTest->m_value = 10;
}
doSomething
procedure! We would assume than in doSomething
some reference counter for our pointer is incremented, but auto_ptr
has no such thing.doSomething
procedure our pointer gets out of scope and it is deleted. To make it work we need to pass a reference to this auto pointer. Another thing is that we have limited way of deleting more complicated objects, there is no control over it at all, only standard
delete
can be used here.
Why unique_ptr
finally works good?
auto_ptr
to std::unique_ptr<Test>
in our previous example, we will get compile (not runtime) error saying that we cannot pass pointer to other function. And this is the proper behaviour.unique_ptr
is correctly implemented because of move
semantics basically. We can move (but not copy) ownership from pointer to another. We also need to be aware when and where we pass the ownership.In our example we can use:
doSomethig(std::move(myTest));
How to use arrays with unique_ptr?
First thing to know:std::unique_ptr<int> p(new int[10]); // will not work!
delete
will be called. So how do we ensure that delete[]
is called? Fortunately unique pointers have a proper partial specialization for arrays and we can write:std::unique_ptr<int[]> p(new int[10]);
p[0] = 10;
For our particular example:std::unique_ptr<Test[]> tests(new Test[3]);
And we will get the desired output:Test::Test
Test::Test
Test::Test
Test::~Test destructor
Test::~Test destructor
Test::~Test destructor
As expected :)&(pointerToArray[0])
. Writing pointerToArray
will not work.
Why create shared_ptr
with make_shared?
shared_ptr
we need to associate some reference counter with our object. How to do that efficiently?std::shared_ptr<Test> sp(new Test());
std::shared_ptr<Test> sp2 = std::make_shared<Test>();
We will get the output as expected:Test::Test
Test::Test
Test::~Test destructor
Test::~Test destructor
unique_ptr
? The answer lies in the allocation process. With the first construct we need to allocate a space for the object and then for the reference counter. With the second construct there is only one allocation (using placement new) and ref counter shares the same memory block as the pointed object.![]() |
VS 2012 local's view |
make_unique
function ! That way creating smart pointers is a bit more 'unified'. We have make_shared
and make_unique
.
How to use arrays with shared_ptr?
Arrays with shared_ptr
are a bit trickier that when using unique_ptr
, but we can use our own deleter and have full control over them as well:std::shared_ptr<Test> sp(new Test[2], [](Test *p) { delete []p;});
make_shared
construction.How to pass smart pointers to functions?
void testSharedFunc(std::shared_ptr<Test> sp) {
sp->m_value = 10;
}
void testSharedFuncRef(const std::shared_ptr<Test> &sp) {
sp->m_value = 10;
}
void SharedPtrParamTest() {
std::shared_ptr<Test> sp = std::make_shared<Test>();
testSharedFunc(sp);
testSharedFuncRef(sp);
}
testSharedFuncRef
we get no benefit of using shared pointers at all! Only testSharedFunc
will increase reference counter.For some performance critical code we, additionally, need to notice that passing by value will need to copy whole pointer block, so maybe it is better to use even raw pointer there.
But, perhaps the second option (with reference) is better? The answer: it depends. The main question is if you want to have full ownership of the object or you want to just 'observe' the object. If observe(for instance you have some generic function that calls methods of the object) then we do not need ownership... simple passing by reference is good and fast method.
How to cast smart pointers?
Let's take a common example with a simple inheritance:
class BaseA { protected: int a{ 0 }; public: virtual ~BaseA() { } void A(int p) { a = p; } }; class ChildB : public BaseA { private: int b{ 0 }; public: void B(int p) { b = p; } };
Without a problem, you can create a smart pointer to BaseA and initialize it with ChildB:
std::shared_ptr<BaseA> ptrBase = std::make_shared<ChildB>(); ptrBase->A(10);
But how to get a pointer to a ChildB class from ptrBase
? Although, it is not a good practice, sometimes we know it is needed.
You can try this:
ChildB *ptrMan = dynamic_cast<ChildB *>(ptrBase.get()); ptrMan->B(10);
It should work. But, that way you get a 'normal' pointer only! The use_count
for the original ptrBase
is not incremented. You can now observe the object, but you are not the owner.
It is better to use casting functions designed for smart pointers:
std::shared_ptr<ChildB> ptrChild = std::dynamic_pointer_cast<ChildB>(ptrBase); if (ptrChild) { ptrChild->B(20); std::cout << "use count A: " << ptrBase.use_count() << std::endl; std::cout << "use count B: " << ptrChild.use_count() << std::endl; }
by using std::dynamic_pointer_cast you get a shared pointer. Now you are also the owner. Use count for ptrBase
and ptrChild
is '2' in this case.
Take a look at std::static_pointer_cast and std::const_pointer_cast for more information.
What about unique_ptr?
In the previous example you got a copy of the original pointer. But unique_ptr
cannot have copies... so it is no sense to provide a casting functions. If you need a casted pointer for observation then you need to do it the old way.
Some additional comments
Code for the article: https://github.com/fenbf/review/blob/master/smart_ptr.cpp
Links
- Book: The C++ Standard Library: A Tutorial... (2nd)
was the main reference for this post.
- Book: The C++ Programming Language, 4th Edition
- Book: C++ Primer (5th Edition)
- SO: How do I pass a unique_ptr argument to a constructor or a function?
- SO: Stop heap allocation via make_shared
- SO: Is make_shared really more efficient than new?
- Smart Pointers - What, Why, Which? @ootips