What is shared_ptr

beginner c++11 memory

Related: Unique Pointer and The Heap

In C++ std::shared_ptr (pronounced “shared pointer”) is one of the “smart pointer” classes available in the Standard Library. What makes std::shared_ptr “smart” is that it always calls delete on the pointer it holds onto on destruction as compared to regular pointers, which do not. std::shared_ptr implements the dereference operator (*) and the arrow operator (->) in order to behave like a pointer even though it’s really an object from a library.

The Standard Library also provides a helper function for creating shared pointers called std::make_shared(). std::make_shared() calls new for you, passing all parameters to the type’s constructor. This function is useful because objects might throw exceptions during construction and if they do we still need to call delete on the pointer we received from new otherwise we will leak the pointer. std::make_shared() correctly handles these cases. This is why it’s considered best practice to always use std::make_shared() instead of calling new. Here’s an example:

#include <memory>
#include <iostream>

struct Person {
  Person(std::string name, int age) 
    : name(std::move(name)), age(age) {
  }

  ~Person() {
    std::cout << name << " says bye!\n";
  }

  std::string name;
  int age;
};

void describe_person(const Person& p) {
  std::cout << p.name << " is " << p.age << "\n";
}

int main() {
  // Best to use std::make_shared
  auto maria = std::make_shared<Person>("Maria", 40); 
  // You can also pass any pointer to std::shared_ptr's constructor
  Person* p = new Person("Nushi", 12);
  std::shared_ptr<Person> nushi(p);
  
  // shared_ptr can be used just like regular pointers
  // because of operator overloading
  describe_person(*maria);
  describe_person(*nushi);

  nushi->age += 1;
  describe_person(*nushi);
}
Maria is 40
Nushi is 12
Nushi is 13
Nushi says bye!
Maria says bye!

Why might you want a shared pointer? Sometimes your program may have large pieces of data, like images or sounds, that you would like to re-use again and again. std::shared_ptr can be great for sharing these large objects in your program.

shared_ptr Is for Sharing

std::shared_ptr shares ownership of its pointer with its copies, unlike std::unique_ptr which uniquely owns its pointer and cannot be copied. std::shared_ptr does this by using reference counting. Every copy of a shared pointer increases the reference count by 1 and every destructor decreases the reference count by 1. When the last copy of a shared pointer is destroyed that copy’s destructor calls delete on the owned pointer, ensuring delete is always properly called, but only once. Here’s an example:

#include <memory>
#include <iostream>

struct Person {
  Person(std::string name, int age) 
    : name(std::move(name)), age(age) {
  }

  ~Person() {
    std::cout << name << " says bye!\n";
  }

  std::string name;
  int age;
};

void describe_person(const Person& p) {
  std::cout << p.name << " is " << p.age << "\n";
}

int main() {
  // Best to use std::make_shared
  auto maria = std::make_shared<Person>("Maria", 40); 

  {
    std::shared_ptr<Person> alsoMaria = maria;
    std::shared_ptr<Person> mariaAgain = alsoMaria;

    describe_person(*maria);
    maria->age += 1;
    describe_person(*alsoMaria);

    std::cout << maria.use_count() << " shared pointers\n";
  }
  std::cout << maria.use_count() << " shared pointer\n";
}
Maria is 40
Maria is 41
3 shared pointers
1 shared pointer
Maria says bye!

get()

Sometimes an API demands a pointer be passed as a parameter. std::shared_ptr provides a get() method for just such a purpose. get() returns the underlying pointer, but be careful, the ownership of the pointer still belongs to the std::shared_ptr object and its copies. The pointer returned by get() does not increase nor decrease the reference count.

#include <memory>
#include <iostream>

struct Person {
  Person(std::string name, int age) 
    : name(std::move(name)), age(age) {
  }

  ~Person() {
    std::cout << name << " says bye!\n";
  }

  std::string name;
  int age;
};

void describe_person(Person* p) {
  std::cout << p->name << " is " << p->age << "\n";
}

int main() {
  auto maria = std::make_shared<Person>("Maria", 40); 

  describe_person(maria.get());
}
Maria is 40
Maria says bye!


For more C++ By Example, click here.