std::shared_ptr
¶
The Spirit Of std::shared_ptr
¶
Shared ownership: multiple pointer objects reference one object that is shared
Pointer copy creates one more reference
⟶ Increases reference count
Last remaining reference is responsible for deallocation
Notes About Thread Safety¶
Reference count is thread-safe
⟶ Creating new references (i.e. copying pointer objects) is a little more expensive than a simple integer increment
Pointer object itself is not thread-safe
⟶ Concurrent writes lead to undefined behavior
See Atomic Shared Pointer (std::atomic<std::shared_ptr<>) for a “workaround”
Creating std::shared_ptr
Instances¶
Raw pointer at the basis
std::shared_ptr<>
wrapped around⟶ “Control block” created: reference count
Reference count initialized to one
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(shared_ptr_suite, basic_creation)
{
BigData* data = new BigData(100, 'a');
std::shared_ptr<BigData> pdata(data);
ASSERT_EQ(pdata.use_count(), 1); // <-- pdata is the only reference
}
More condensed usage (just like
std::unique_ptr<>
)Convenience function: allocates object of type T, and forward arguments to constructor
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(shared_ptr_suite, more_condensed_creation)
{
auto pdata = std::make_shared<BigData>(100, 'a');
}
Copying std::shared_ptr
Instances¶
Copying is what shared pointers are there for
Each pointer copy adds one reference to the object
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(shared_ptr_suite, copy)
{
auto pdata = std::make_shared<BigData>(100, 'a');
auto copy = pdata;
ASSERT_EQ(pdata.get(), copy.get()); // <-- both point to same object
ASSERT_EQ(pdata.use_count(), 2); // <-- pointed-to object has one more reference
}
Object Lifetime (“Garbage Collection”)¶
How long does the pointed-to object live?
Reference count is used to track shared ownership
When reference count drops to zero, the object is not referenced anymore
⟶ deallocated
Examining the reference count
The zero-transition of the reference count is the only event in the object lifecycle that can be counted on
Everything else is dangerous when the object is shared across threads
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(shared_ptr_suite, refcount_if)
{
auto pdata = std::make_shared<BigData>(100, 'a');
auto copy = pdata;
if (pdata.use_count() == 2) // <-- there be dragons!
/*do something*/;
}
Raw Pointer Access: .get()
¶
Not the plan
Sometimes needed though
⟶ Careful!
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(shared_ptr_suite, raw_pointer_get)
{
auto pdata = std::make_shared<BigData>(100, 'a');
BigData* raw = pdata.get();
ASSERT_EQ(raw->at(42), 'a'); // <-- operator->()
ASSERT_EQ((*raw).at(42), 'a'); // <-- operator*()
ASSERT_EQ(raw, pdata.get()); // <-- .get() leaves pdata untouched
}
Unreferencing Objects: .reset()
¶
Nulling out pointer object
Drop reference on object (decrease reference count)
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(shared_ptr_suite, reset)
{
auto pdata = std::make_shared<BigData>(100, 'a');
auto copy = pdata; // <-- create second reference
pdata.reset(); // <-- drop one reference on original
ASSERT_EQ(pdata, nullptr);
ASSERT_EQ(copy.use_count(), 1);
copy.reset(); // <-- drop last reference (deallocated object)
}
Variant: exchanging pointer-to object with different (or even same) object
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(shared_ptr_suite, reset_exchange)
{
auto pdata = std::make_shared<BigData>(100, 'a');
auto copy = pdata;
BigData* newdata = new BigData(1000, 'b');
pdata.reset(newdata); // <-- unref original, ref new
ASSERT_EQ(pdata.get(), newdata);
ASSERT_EQ(copy.use_count(), 1);
}
Custom Deleter¶
Deleter not part of the type - as opposed to
std::unique_ptr
⟶ would prevent sharing, obviously
Delete stored in control block
⟶ Each shared object can have its own deleter
.get_deleter()
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(shared_ptr_suite, deleter)
{
bool deleted1 = false;
auto deleter1 = [&deleted1](BigData* obj) {
deleted1 = true;
delete obj;
};
std::shared_ptr<BigData> pdata(new BigData(100, 'a'), deleter1); // <-- ctor parameter
bool deleted2 = false;
auto deleter2 = [&deleted2](BigData* obj) {
deleted2 = true;
delete obj;
};
BigData* newdata = new BigData(100, 'b');
pdata.reset(newdata, deleter2); // <-- unref (and delete) original, ref new
ASSERT_TRUE(deleted1);
pdata.reset(); // <-- unref (and delete) new data
ASSERT_TRUE(deleted2);
}
Cyclic References¶
Cyclic references are not detected
⟶ Memory leak
#include <memory>
class NonSense
{
public:
void set_reference(std::shared_ptr<NonSense> ref)
{
_ref = ref;
}
private:
std::shared_ptr<NonSense> _ref;
};
int main()
{
std::shared_ptr<NonSense> cycle(new NonSense);
cycle->set_reference(cycle);
return 0;
}
$ valgrind ./c++11-shared-ptr-cyclic
...
==888857== HEAP SUMMARY:
==888857== in use at exit: 40 bytes in 2 blocks
==888857== total heap usage: 3 allocs, 1 frees, 73,768 bytes allocated
==888857==
==888857== LEAK SUMMARY:
==888857== definitely lost: 16 bytes in 1 blocks
==888857== indirectly lost: 24 bytes in 1 blocks
==888857== possibly lost: 0 bytes in 0 blocks
==888857== still reachable: 0 bytes in 0 blocks
==888857== suppressed: 0 bytes in 0 blocks
...