Shared ownership is a model where several owners can own the same object simultaneously. The object is only destroyed when the last owner goes away. This notion is formalized in the C++ standard library by the smart pointer type std::shared_ptr.
A std::shared_ptr<T> maintains a pointer to an object of type T along with a reference count. The count is increased when a new shared_ptr pointing to the same object is created and decreased when such a shared_ptr is destroyed. The object is deleted when the count reaches zero. Since ownership is shared, shared_ptrs can be copied as well as moved.
The preferred method of creating a shared_ptr is through the function std::make_shared. It performs a single allocation for the object and the control block that holds the reference counter and then returns a shared_ptr that owns the object. A minimal complete program is:
#include <iostream>
#include <memory>
class Tracer
{
public:
Tracer()
{
std::cout << "Tracer constructed\n";
}
// Run this destructor when the object lifetime ends
~Tracer()
{
std::cout << "Tracer destroyed\n";
}
void hello() const
{
std::cout << "hello from Tracer\n";
}
};
int main()
{
std::shared_ptr<Tracer> p{ std::make_shared<Tracer>() };
p->hello();
std::cout << "use count is "
<< p.use_count()
<< '\n';
return 0;
}
std::make_shared<Tracer>() constructs a Tracer and a control block, then returns a shared_ptr that owns both. The variable p is the sole owner so p.use_count() is 1. The expression p->hello() calls the member function on the owned object. When main ends, the destructor of p decrements the reference count to zero and the Tracer is deleted, printing "Tracer destroyed".
Since ownership is shared, a second shared_ptr can be constructed from an existing shared_ptr without transferring ownership. Each shared_ptr now owns the same object and there is a reference count of 2.
#include <iostream>
#include <memory>
class Tracer
{
public:
Tracer()
{
std::cout << "Tracer constructed\n";
}
~Tracer()
{
std::cout << "Tracer destroyed\n";
}
};
int main()
{
// Create the object with shared ownership
std::shared_ptr<Tracer> p1{ std::make_shared<Tracer>() };
std::shared_ptr<Tracer> p2{ p1 };
std::cout << "use count is "
<< p1.use_count()
<< '\n';
std::cout << "use count is "
<< p2.use_count()
<< '\n';
return 0;
}
The line std::shared_ptr<Tracer> p2{ p1 }; copies p1. The copy constructor of shared_ptr will bump the reference count, hence p1.use_count() and p2.use_count() both print 2. The two smart pointers are separate owners of the same Tracer.
When main finishes, the destructors of p2 and p1 run in that order.
Each one decrements the count; only the last decrement to bring it to 0 actually causes the Tracer to be destroyed; the message "Tracer destroyed" only appears once.
It is also possible to move a shared_ptr, and it costs less than copying because it does not touch the reference count. When moving, the source becomes empty, the destination now owns the ownership it already had, or receives the unique ownership of the source.
A function can accept a shared_ptr by value, making it a new owner during the function call.
#include <iostream>
#include <memory>
class Tracer
{
public:
Tracer()
{
std::cout << "Tracer constructed\n";
}
// Run this destructor when the object lifetime ends
~Tracer()
{
std::cout << "Tracer destroyed\n";
}
};
void observe(std::shared_ptr<Tracer> p)
{
std::cout << "inside function use count is "
<< p.use_count()
<< '\n';
}
int main()
{
std::shared_ptr<Tracer> p{ std::make_shared<Tracer>() };
std::cout << "before call use count is "
<< p.use_count()
<< '\n';
observe(p);
std::cout << "after call use count is "
<< p.use_count()
<< '\n';
return 0;
}
The call observe(p) makes a copy of p into the function parameter, and the reference count becomes 2. While inside the function, the copy of p is kept alive. When the function returns, the parameter is destroyed, and p now has only one owner again. When that owner is destroyed, the Tracer is destroyed.
Hence std::shared_ptr offers automatic shared ownership. The managed object lives for as long as there exists a shared_ptr that owns it, and it is deleted exactly once when the last owner goes away. Creating with std::make_shared keeps the allocation cheap, copying adds owners, moving transfers ownership without affecting count, and the reference counting ensures that the object won't be leaked or prematurely deleted. The resulting code states shared lifetime naturally and safely.
Exercise: Shared Ownership with std::shared_ptr and std::make_sharedStarter code, hints and solutionOpenClose
Practice: Shared Ownership with std::shared_ptr and std::make_shared
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
Reset p1, print the remaining shared-owner count, then reset p2 and print a completion message. Predict when the object is destroyed.
Mission checklist
- Run the starter as a baseline, then predict the change requested in Your challenge.
- Make that change in your editor; use the lesson-specific hints if you need help.
- Compile and run your answer, then compare with the worked solution and explain the result.
Starter program
This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.
#include <iostream>
#include <memory>
class Tracer
{
public:
Tracer()
{
std::cout << "Tracer constructed\n";
}
~Tracer()
{
std::cout << "Tracer destroyed\n";
}
};
int main()
{
// Create the object with shared ownership
std::shared_ptr<Tracer> p1{ std::make_shared<Tracer>() };
std::shared_ptr<Tracer> p2{ p1 };
std::cout << "use count is "
<< p1.use_count()
<< '\n';
std::cout << "use count is "
<< p2.use_count()
<< '\n';
return 0;
}Hints
p2 is copied from p1, so both shared_ptr objects own the same Tracer and share one control block. The copy does not call another Tracer constructor.
Release one shared_ptr before checking the other's use_count. The last reset, not the first, ends the managed object's lifetime.
Worked solution
Resetting p1 removes one shared owner, leaving p2 with count 1. Resetting p2 releases the last owner and destroys the Tracer before All owners released. Copying a shared_ptr did not create a second Tracer.
#include <iostream>
#include <memory>
class Tracer
{
public:
Tracer()
{
std::cout << "Tracer constructed\n";
}
~Tracer()
{
std::cout << "Tracer destroyed\n";
}
};
int main()
{
// Create the object with shared ownership
std::shared_ptr<Tracer> p1{ std::make_shared<Tracer>() };
std::shared_ptr<Tracer> p2{ p1 };
std::cout << "use count is "
<< p1.use_count()
<< '\n';
std::cout << "use count is "
<< p2.use_count()
<< '\n';
p1.reset();
std::cout << "Remaining owners: " << p2.use_count() << '\n';
p2.reset();
std::cout << "All owners released\n";
return 0;
}
Example output
Tracer constructed
use count is 2
use count is 2
Remaining owners: 1
Tracer destroyed
All owners released
0 comments
No comments yet. Be the first.