0

I am trying to understand which of the following usage of shared pointer makes more sense as it gets inserted into a vector.

bar takes a const reference of a shared pointer vs foo that takes a copy. In both the cases, the passed-in parameter gets moved into a vector. The interesting part is the use_count of a in the caller remains 2 for foo and bar both which implies the the vector stores a "copy"?

Like if a shared_ptr is passed by a reference, its count doesn't increment. As soon as it's "moved" into a vector, it does. Does that mean vector isn't storing the reference to an original object a?

class A
{
    std::vector<std::shared_ptr<int>> _vec;

    public:
    void bar(const std::shared_ptr<int>& ptr)
    {
        _vec.emplace_back(std::move(ptr));
    }

    void foo(std::shared_ptr<int> ptr)
    {
        _vec.emplace_back(std::move(ptr));
    }
};


int main()
{
    auto sp = std::make_shared<int>();

    A a;
    // a.foo(sp);    // use_count = 2
    a.bar(sp);       // use_count = 2
}
xyf
  • 664
  • 1
  • 6
  • 16
  • 1
    `_vec.emplace_back(std::move(ptr));` makes a copy of the shared pointer. It's not "moved". – Ted Lyngmo Dec 13 '22 at 18:03
  • Are you saying it's no different than `_vec.emplace_back(ptr);`? – xyf Dec 13 '22 at 18:06
  • Yes, except if there's a minor difference between the actual `push_back` and `emplace_back`. They will both copy `ptr` if it's a `const&`. If you take it by value, it'll move it though, but that doesn't have anything to do with `push_back`/`emplace_back`. You can visualize it [like this](https://godbolt.org/z/P1ff5xxs9) – Ted Lyngmo Dec 13 '22 at 18:11
  • so a move constructor doesn't really end up being called? why would that really be? – xyf Dec 13 '22 at 18:15
  • Yes, if you do like in `foo` and take it by value the `shared_ptr` move constructor will be used when you do `emplace_back` (or `push_back`). My first comment was about the `bar` function. I should have made that clear. – Ted Lyngmo Dec 13 '22 at 18:16
  • which option is better: pass a `const &` of a `shared_ptr` and `push_back` it or pass a copy of a shared_ptr and `std::move` into a `vector`? – xyf Dec 13 '22 at 18:28
  • The answers in [should-we-pass-a-shared-ptr-by-reference-or-by-value](https://stackoverflow.com/questions/3310737/should-we-pass-a-shared-ptr-by-reference-or-by-value) seems to suggest references. You could have overloads. One for `const&` and one for `&&`. I never use `shared_ptr` myself so I don't have any `shared_ptr` best practices in my back pocket unfortunately. – Ted Lyngmo Dec 13 '22 at 18:30

2 Answers2

1

You're passing the shared_ptr to bar by reference to const. That means that the original shared_ptr can't be modified via that reference.

Moving from a share_ptr requires modifying the moved-from shared_ptr to set it to point to nothing.

See the issue? bar can't move from ptr, so it instead ends up copying it into the vector. ptr/sp remains valid and continues to point to the int that you allocated in main and a._vec also holds a shared_ptr to that same int. Thus use_count must be 2.

If you want to actually move from sp then you should change bar to accept some sort of mutable reference. Really you should make it accept an rvalue reference though, since a.bar(sp) causing sp to become invalid would violate most programmers' expectations:

class A
{
    std::vector<std::shared_ptr<int>> _vec;

    public:
    void bar(std::shared_ptr<int>&& ptr)
    {
        _vec.emplace_back(std::move(ptr));
    }
};


int main()
{
    auto sp = std::make_shared<int>();

    A a;
    a.bar(std::move(sp));
    // Here sp.use_count() is 0 because it was moved from
    // a._vec.back().use_count() will be 1 though
}

Demo


This limits the caller to always moving their shared_ptr into A. Simply accepting the parameter by value as you do in foo will likely result in no measurable performance difference when the caller wants to give up ownership and provides greater flexibility when they don't.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • Makes sense that `const` can't be moved since you can't steal/modify from a const object. Question though: if a copy was passed as a parameter and move was applied, wouldn't that render its count to be 1? So here, `foo` is called which takes a copy and invokes move ctor. Wouldn't that render `sp` in the caller 'useless' since it's been moved into `_vec`, hence the `use_count`? or is it just that `sp` in the caller shouldn't be used anyway? https://godbolt.org/z/YY3ecoP1a – xyf Dec 14 '22 at 06:40
  • @xyf It depends. If you call `a.foo(sp)` then `sp` is copied, so `sp` and `ptr` point to the same object. Hence both have a `use_count` of 2. `ptr` is then moved into `_vec`, so now `sp` and `_vec.back()` both point to the same object and `ptr` points to nothing. That gives `sp` and `_vec.back()` both a `use_count` of 2 and `ptr` a `use_count` of 0. If you call `a.foo(std::move(sp))` then `sp` gets moved to `ptr` and so `sp` points to nothing and `ptr` points to the object `sp` used to point to. `ptr` is then moved into `_vec` leaving it pointing to nothing. – Miles Budnek Dec 14 '22 at 07:41
-1

In both cases, the shared pointer being passed to foo and bar is being moved into the vector. This means that the vector takes ownership of the dynamically allocated memory that the shared pointer was managing, and the shared pointer in the caller is no longer managing any memory.

The difference between foo and bar is that foo takes a copy of the shared pointer, while bar takes a const reference to the shared pointer. Since foo takes a copy, the shared pointer's reference count is incremented by one when it is passed to foo. This means that the reference count will be 2 after foo is called.

In contrast, bar takes a const reference to the shared pointer, so the reference count is not incremented when it is passed to bar. This means that the reference count will remain at 1 after bar is called.

Overall, the use of bar is more efficient in this case because it does not require an extra increment of the reference count, but the difference in performance is likely to be small in practice. It is more important to choose the approach that is more readable and maintainable for your specific use case.

Vin Vin
  • 1
  • 1
  • The last paragraph is not 100% accurate. `foo()`'s parameter can be move-constructed. In which case the reference count is completely unchanged, and never gets incremented or decremented. – Sam Varshavchik Dec 13 '22 at 18:08
  • *"This means that the reference count will remain at 1 after bar is called"* except for the fact that the shared_ptr is *copied* into `_vec` in both cases, and therefore increases the reference count. – Cory Kramer Dec 13 '22 at 18:08