Exercise
Below is a very long page of exercises related to using smart pointers, varying in difficulty. Click on 'solution' to see the answer to the questions. You can put the questions 'in action' by writing small C++ programs, but they are also answerable by just inspecting the code snippets.
Question
Smart Pointers allow you to access the underlying pointer, but they don't make you do it very often. However, it's common for those recently acquainted with smart pointers to ignore the overloaded operators out of either ignorance or a misplaced fear of performance degradation. Change the following code using .get()
to have a more natural syntax:
std::unique_ptr<Bar> pBar(new Bar);
foo(*(pBar.get()));
pBar.get()->x();
if(pBar.get())
{
//Do some stuff...
}
SolutionClick to expand
Question
When should .get()
be used on a smart pointer?
SolutionClick to expand
Question
unique_ptr::release
has a similar signature to unique_ptr::get
. How is it different?
SolutionClick to expand
Question
It would be possible to allow the following code to compile. What could happen if shared_ptr
or unique_ptr
were changed to allow it?
void foo1(int*) {}
std::shared_ptr<int> pInt(new int(5));
foo1(pInt);
SolutionClick to expand
Question
It would be possible to allow the following code to compile. What could happen if shared_ptr
or unique_ptr
were changed to allow it?
std::unique_ptr<int> pInt2 = new int(5);
SolutionClick to expand
Question
The following contains a mistake. Fix it, and explain why it was wrong.
std::shared_ptr<int> p1(new int(42));
std::shared_ptr<int> p2(p1.get());
SolutionClick to expand
Question
The following contains a mistake. Fix it, and explain why it was wrong.
std::unique_ptr<int> p3(new int[42]);
SolutionClick to expand
Question
When would you use a unique_ptr
versus a shared_ptr
?
SolutionClick to expand
Question
Suppose you have a class which returns a pointer which the user may wish to store in a shared_ptr
. What type should you return?
SolutionClick to expand
Question
The following code opens a handle to a file using the C-style FILE*
interface
and closes the handle before returning.
FILE* pFin = NULL;
try
{
std::string filename("c:\\Windows\\win.ini");
pFin = std::fopen(filename.c_str(), "r");
if (!pFin)
return;
char data[10];
if (std::fread(data, 1, 10, pFin) <= 0)
{
std::fclose(pFin);
return;
}
std::fclose(pFin);
}
catch (...)
{
if (pFin)
std::fclose(pFin);
throw; //rethrow after closing file
}
We could change it to use
iostreams, but for this exercise, change the code to just use a smart
pointer (std::shared_ptr
) to FILE
with a custom deleter, so that the
fclose()
call doesn't have to appear in so many places, and eliminate the
try-catch block.
SolutionClick to expand
Question
What could you do if your deleter had a declaration that didn't match the
void(T*)
interface that the smart pointers expect?
SolutionClick to expand
Question
Why is the weak_ptr
class designed such that we have to convert the weak_ptr
into a shared_ptr
before using the object the weak pointer refers to?
SolutionClick to expand
Question
Assuming that p_int
's lifetime is managed in a separate thread of execution, what could happen if the code were structured like this:
if (!p_weak_int.expired())
{
auto p_tmp_copy = p_weak_int.lock();
std::cout << "value = " << *p_tmp_copy << std::endl;
}
else
std::cout << "expired\n";
SolutionClick to expand
Question
In the un-threaded example above, a raw pointer would've served just as well as a
std::weak_ptr
. However, suppose we had 3 threads, each owning one copy
of the std::shared_ptr
(so its reference count is 3). Suppose each thread
could terminate at any time (thus decreasing the reference count until it
reaches 0 and the memory is freed). The main thread has a std::weak_ptr
copy and is monitoring/reporting its value periodically.
Now what benefit(s) might using a std::weak_ptr
provide over using a copy
of the raw pointer?
SolutionClick to expand
Question
auto p_unique = get_unique_ptr(my_lib.open_handle(),
[&](int* p_handle) { my_lib.close_handle(p_handle); });
//Now suppose we need to change to a shared_ptr:
std::shared_ptr<int> p_shared(p_unique.release());
What's wrong with the conversion to shared_ptr
and why does it cause a
crash?
SolutionClick to expand
Question
The fix for the above looks like this:
std::shared_ptr<int> p_shared(std::move(p_unique));
Rewrite the code above. Why do you have to use std::move()
?
SolutionClick to expand
Question
When we copied a shared_ptr
to another shared_ptr
, we didn't have to use
std::move()
. Why not?
SolutionClick to expand
Question
//We have a unique_ptr to const and we want to switch to a unique_ptr to
// non-const:
std::unique_ptr<const int> p_const_unique(new int(5));
//std::unique_ptr<int> p_my_non_const_int(std::move(p_const_unique));
If you uncomment the line above, it won't compile. Why not?
SolutionClick to expand
Question
//We have a shared_ptr and we want to convert it to a unique_ptr:
std::shared_ptr<int> p_shared_int(new int(6));
//std::unique_ptr<int> p_unique_int(std::move(p_shared_int));
If you uncomment the line above, it won't compile. Why not?
You can create a shared_ptr
from a unique_ptr
, so why can't you create a
unique_ptr
from a shared_ptr
?
SolutionClick to expand
Question
Consider the following code:
boost::shared_ptr<int> int1(new int(42));
How many allocations were required? What was the size of each allocation?
SolutionClick to expand
Question
How many allocations were required? What was the size of each allocation?
SolutionClick to expand
Question
Consider the following code:
int foo() { throw std::runtime_error("Normally conditional"); return 5; }
void bar(std::shared_ptr<int> int, int val)
{}
void example2()
{
bar(std::shared_ptr<int>(new int(42)), foo());
}
Why could calling example2 result in a memory leak?
SolutionClick to expand
Question
std::make_shared
always uses the std::allocator
to retrieve memory. What should we use if we have a different allocator?
SolutionClick to expand
Question
Consider the following two ways to allocate an array:
void example3()
{
int* int1 = new int[42];
delete [] int1;
std::shared_ptr<int> int2(new int[42], [](int* p) { delete [] p; });
}
Both have drawbacks. Using boost::shared_ptr
as of 1.53 you can write:
boost::shared_ptr<int[]> int3(new int[42]);
or
auto int4 = boost::make_shared<int[]>(42);//make_shared_noinit is actually functionally equivalent. . .
The drawbacks to the code for int1 in example3 are obvious, since RAII classes protect against memory leaks. int2 has drawbacks that aren't quite as obvious. Name at least one drawback to storing an array in a shared_ptr
.