C++ default semantics often involve copying, which can sometimes lead to unintended consequences. Let’s explore this through a practical example involving shared pointers.

The Pitfall

Consider the following scenario where we create a shared pointer to a set of strings:

#include<iostream>
#include<set>
#include<memory>

int main() {
  typedef std::set<std::string> STRINGSET;
  STRINGSET names = {"Tom", "Tim", "Sam"};
  auto names_sptr = std::make_shared<STRINGSET>(names);
  {
    auto names_ptr_cpy = names_sptr;
    std::cout << "Share pointer use: " << names_sptr.use_count() << std::endl;  // Share pointer use: 2
  }
  std::cout << "Share pointer use: " << names_sptr.use_count() << std::endl;    // Share pointer use: 1
  return 0;
}

Here, when we copy the shared pointer names_sptr, the use count increments, indicating that there are now two pointers referencing the set. So far, everything seems to be working as expected.

The Issue Arises

Now, let’s attempt to modify the set and observe what happens:

#include<iostream>
#include<set>
#include<memory>

int main() {
  typedef std::set<std::string> STRINGSET;
  STRINGSET names = {"Tom", "Tim", "Sam"};
  auto names_sptr = std::make_shared<STRINGSET>(names);
  {
    auto names_sptr_cpy = names_sptr;
    std::cout << "Share pointer use: " << names_sptr.use_count() << std::endl; // 2
    auto names = *names_sptr_cpy.get();
    names.insert("Sawn");
    for (const auto &name : *names_sptr_cpy.get())
    {
      std::cout << "Name: " << name << std::endl;
    }
    /* ---- Output ------
    Share pointer use: 2
    Name: Sam
    Name: Tim
    Name: Tom
    Share pointer use: 1
    ---------------------*/
    // Where is "Sawn ??
  }
  std::cout << "Share pointer use: " << names_sptr.use_count() << std::endl; // 1
  return 0;
}

Despite our expectation that “Sawn” would be added to the set, it doesn’t appear when we print the set. The reason for this discrepancy becomes apparent upon closer examination.

The Root Cause

In the line auto names = *names_sptr_cpy.get();, we’re inadvertently making a copy of the set. This action essentially retrieves the underlying set from names_sptr_cpy shared pointer and then duplicates it to the names variable within this scope.

The Solution

To rectify this issue, we simply need to change the line to auto *names = names_sptr_cpy.get();. By marking names as a pointer type, we can modify the same set without creating a copy.

► Run this code

#include<iostream>
#include<set>
#include<memory>

int main() {
  typedef std::set<std::string> STRINGSET;
  STRINGSET names = {"Tom", "Tim", "Sam"};
  auto names_sptr = std::make_shared<STRINGSET>(names);
  {
    auto names_sptr_cpy = names_sptr;
    std::cout << "Share pointer use: " << names_sptr.use_count() << std::endl; // 2
    auto *names = names_sptr_cpy.get();
    names->insert("Sawn");
    for (const auto &name : *names_sptr_cpy.get())
    {
      std::cout << "Name: " << name << std::endl;
    }
    /* ---- Output ------
    Share pointer use: 2
    Name: Sam
    Name: Sawn
    Name: Tim
    Name: Tom
    Share pointer use: 1
    ---------------------*/
  }
  std::cout << "Share pointer use: " << names_sptr.use_count() << std::endl; // 1
  return 0;
}

By making this adjustment, we ensure that modifications made to names directly impact the underlying set referenced by the shared pointer.