4

Why is shared_ptr<drived> counter incremented when I pass it to a function that expects a const shared_ptr<base>&?

In this question one of the answers mentions:

shared_ptr<Base> and shared_ptr<Derived> are not covariant

I suspect that this is relevant to my question. What does it mean that they are not covariant?

Here is a code snippet to show case the scenario:

#include <iostream>
#include <memory>

class Base {};

class Derived : public Base {};

void f(const std::shared_ptr<Base>& x)
{
    std::cout << "in function expecting const shared_ptr<Base>& - Use count: " << x.use_count() << std::endl;
}

int main(int argc, char const *argv[])
{
    std::cout << "Base class" << std::endl;
    auto a = std::make_shared<Base>();
    std::cout << "Created shared_ptr:  Initial use count: " << a.use_count() << std::endl;
    f(a);

    std::cout << "------------------\nChild class" << std::endl;
    auto b = std::make_shared<Derived>();
    std::cout << "Created shared_ptr. Initial use count: " << b.use_count() << std::endl;
    f(b);

    return 0;
}

Results in:

>> g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
Base class
Created shared_ptr:  Initial use count: 1
in function expecting const shared_ptr<Base>& - Use count: 1
------------------
Child class
Created shared_ptr. Initial use count: 1
in function expecting const shared_ptr<Base>& - Use count: 2
pooya13
  • 2,060
  • 2
  • 23
  • 29

1 Answers1

7

A shared_ptr<Derived> is not a shared_ptr<Base>. They are completely different types.

In order to get a shared_ptr<Base> from a shared_ptr<Derived> you need to create one. The compiler can put in a call to the constructor because it isn't marked explicit. This will increase the use count because they share ownership.

template< class Y > shared_ptr( const shared_ptr<Y>& r ) noexcept;

Constructs a shared_ptr which shares ownership of the object managed by r. If r manages no object, *this manages no object too. The template overload doesn't participate in overload resolution if Y* is not implicitly convertible to (until C++17) compatible with (since C++17) T*.

You can see for yourself that a new shared_ptr is created by changing f() to take a non-const reference. The compiler should give you an error because you can't bind a temporary to a non-const reference. See here

Kevin
  • 6,993
  • 1
  • 15
  • 24
  • "A shared_ptr is not a shared_ptr. They are completely different types.... This will increase the use count because they share ownership." How can two objects with two different types be owned by the same shared_ptr? – pooya13 Jun 27 '19 at 21:52
  • See my edit. The constructor only applies if a `Derived*` is implicitly convertible to a `Base*` (and they are). A `shared_ptr` sharing ownership to a `Derived` is perfectly valid. – Kevin Jun 27 '19 at 21:55
  • @pooya13 The only thing that's owned by anything else is the single `Derived` object. There are two objects with two different types, the two `shared_ptr`'s, but they aren't owned by anything. Yes, two `shared_ptr`s of two different types can share ownership of the same object just as two `shared_ptr`s of the same type can. – David Schwartz Jun 27 '19 at 22:10
  • What is an explicit copy ctor? What would that even means? – curiousguy Jul 04 '19 at 18:50
  • @curiousguy https://en.cppreference.com/w/cpp/language/explicit. I suppose it's not a copy constructor because they're different types. I've edited my answer – Kevin Jul 04 '19 at 19:13