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.

SolutionClick to expand

results matching ""

    No results matching ""