Search code examples
c++c++11shared-ptrsmart-pointers

Understanding when shared_ptr reference counts are incremented when passing into a function


I have the following Container class

template <typename T>
class Container
{
private:
    std::vector<std::shared_ptr<T>> items_;
public:
    void addItem(std::shared_ptr<T> item)
    {
        std::cout << "useCount addItem pre: " << item.use_count() << std::endl;
        items_.push_back(std::move(item));
        std::cout << "useCount addItem post: " << item.use_count() << std::endl;
    }
};

and I'm calling it like this

int main(int argc, char** argv) {
    std::unique_ptr<Container<std::string>> container = std::make_unique<Container<std::string>>();

    std::shared_ptr<std::string> s = std::make_shared<std::string>("hello");

    std::cout << "useCount main pre: " << s.use_count() << std::endl;

    container->addItem(s);

    std::cout << "useCount main post: " << s.use_count() << std::endl;

    return 0;
}

This is the output I get

useCount main pre: 1
useCount addItem pre: 2
useCount addItem post: 0
useCount main post: 2

Line by line...

  1. Makes sense, there's only one reference to s as soon as its defined

  2. Makes sense, s gets copied into item so its reference count is incremented by 1

  3. I've given items_ ownership of item, so the reference count shouldn't change since addItem has relinquished ownership and transferred it to items_. I expect the reference count to be 2, once from main and once from items_. Instead it's 0.

  4. Makes sense as long as one reference is from main and one reference is from items_.

Thanks for any clarification!


Solution

  • std::move moves the item out of that variable into another one. As one author put it, it "has explicit license to pillage [the variable]." For most std objects, that puts the original object into an unspecified state. However, it appears std:shared_ptr is an exception in that it leaves the object in an empty state.

    What all this boils down to is that you can't treat item as the same reference to s anymore after the move. It is in a different state, storing something else. That's why your reference count is off.

    If instead you had done this:

    std::cout << "useCount addItem post: " << items_.back().use_count() << std::endl;
    

    You would have got the expected output of 2.