One Trick on shared_ptr

A Glance at shared_ptr and unique_ptr

C++
#include <iostream>
#include <memory>

int main(int argc, char* argv[])
{
  std::cout <<"Size of shared_ptr>> " << sizeof(std::shared_ptr<int>) << '\n'
            <<"Size of unique_ptr>> " << sizeof(std::unique_ptr<int>) << std::endl;

	return EXIT_SUCCESS;
}
 
/*[SYSTEM OUTPUT]----------------------------------------------------------------------
  Size of shared_ptr>> 16
  Size of unique_ptr>> 8
-------------------------------------------------------------------------------------*/
  • As we all know, unique_ptr is rawer than shared_ptr because the latter encapsulates extra data like reference count.
  • So, what has been encapsulated into shared_ptr? (What exactly shared_ptr contains is beyond the scope of this article, you can learn more on other sites)

Start Experimenting

  • One thing contained in shared_ptr is the reference of Deleter. Therefore, the shared_ptr can correctly handle the delete of the class.
  • The experiment below will show this property.
C++
#include <iostream>
#include <memory>

struct A
{
	A()  { std::cout << "create  A\n"; }
	~A() { std::cout << "release A\n"; }
};
struct B
	:public A
{
	B()  { std::cout << "create  B\n"; }
	~B() { std::cout << "release B\n"; }
};

int main(int argc, char* argv[])
{
	std::cout << "\nUniqure Point\n";
	auto uniqueP = std::make_unique<B>();
	uniqueP.reset();

	std::cout << "\nShared Point\n";
	auto sharedP = std::make_shared<B>();
	sharedP.reset();

	return EXIT_SUCCESS;
}

/*[SYSTEM OUTPUT]----------------------------------------------------------------------
  Uniqure Point
  create A
  create B
  release A
  
  Shared Point
  create A
  create B
  release B
  release A
-------------------------------------------------------------------------------------*/
  • Note that, the unique_ptr did not actually call the deconstructor of the subclass because we did not declare that it is virtual, while the shared_ptr even can perfectly handle this scenario without the virtual keyword.
  • That is to say, we can save one virtual function(virtual functions will reduce the performance) when we use shared_ptr as the interface.

Further Reading

An example code in cppreference.com also implied such a situation. (Note the annotation in struct Base)

C++
#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
 
struct Base
{
    Base() { std::cout << "  Base::Base()\n"; }
    // Note: non-virtual destructor is OK here
    ~Base() { std::cout << "  Base::~Base()\n"; }
};
 
struct Derived: public Base
{
    Derived() { std::cout << "  Derived::Derived()\n"; }
    ~Derived() { std::cout << "  Derived::~Derived()\n"; }
};
 
void thr(std::shared_ptr<Base> p)
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::shared_ptr<Base> lp = p; // thread-safe, even though the
                                  // shared use_count is incremented
    {
        static std::mutex io_mutex;
        std::lock_guard<std::mutex> lk(io_mutex);
        std::cout << "local pointer in a thread:\n"
                  << "  lp.get() = " << lp.get()
                  << ", lp.use_count() = " << lp.use_count() << '\n';
    }
}
 
int main()
{
    std::shared_ptr<Base> p = std::make_shared<Derived>();
 
    std::cout << "Created a shared Derived (as a pointer to Base)\n"
              << "  p.get() = " << p.get()
              << ", p.use_count() = " << p.use_count() << '\n';
    std::thread t1(thr, p), t2(thr, p), t3(thr, p);
    p.reset(); // release ownership from main
    std::cout << "Shared ownership between 3 threads and released\n"
              << "ownership from main:\n"
              << "  p.get() = " << p.get()
              << ", p.use_count() = " << p.use_count() << '\n';
    t1.join(); t2.join(); t3.join();
    std::cout << "All threads completed, the last one deleted Derived\n";
}

Leave a Reply

Your email address will not be published. Required fields are marked *