2

According to my understanding, when a const shared_ptr& is upcasted, it creates a new shared pointer of the based class pointing to the same (casted) object.

#include <iostream>
#include <memory>

class Animal
{
};

class Cat : public Animal
{
};

void display(const std::shared_ptr<Animal>& animal)
{
    std::cout << animal.use_count() << std::endl;
}

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

    std::cout << cat.use_count() << std::endl;
    display(cat);
    std::cout << cat.use_count() << std::endl;
}

The output of the above code is as follow.

sujith@AKLJoincggDLEd:~/sujith$ g++  -std=c++17 main.cpp
sujith@AKLJoincggDLEd:~/sujith$ ./a.out
1
2
1
sujith@AKLJoincggDLEd:~/sujith$

My question is what operator does this? Can we get the same behaviour for other reference upcasting as well? Or is it speficially handled for shared_ptr references?

Thank you for looking into this.

Sujith Gunawardhane
  • 1,251
  • 1
  • 10
  • 24
  • 3
    `Can we get the same behaviour for other reference upcasting as well?` Which behavior are you talking about? – tkausl Apr 24 '23 at 09:14
  • Hi @tkausl, Creating a new object in the upcasting a const reference. I did not mean actual such requirement. But for me creating another new object (shared_ptr) in const shared_ptr& casting to const shared_ptr& is a magic. Thank you! – Sujith Gunawardhane Apr 24 '23 at 09:18
  • 1
    please try to focus one 1 single question only. The conversion is via converting constructor (9) from here https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr – 463035818_is_not_an_ai Apr 24 '23 at 09:22
  • 2
    its not magic. Its implicit conversion: https://en.cppreference.com/w/cpp/language/implicit_conversion – 463035818_is_not_an_ai Apr 24 '23 at 09:24
  • 3
    There is an implicit conversion from `shared_ptr` to `shared_ptr`. A temporary object is created through conversion and bound to the reference. The same happens in other implicit conversions. For instance, `int x = 3; const float& y = x;` will "invent" a `float` whose value is `3.0f`. – molbdnilo Apr 24 '23 at 09:25
  • 1
    for more details: https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list. – 463035818_is_not_an_ai Apr 24 '23 at 09:25
  • 1
    You get the same "auto-casting" behavior if you had `void display(Animal const* animal);` and a `auto cat = new Cat;`. The `cat` argument creates an object that is an `Animal const*` which value is set to the `animal` parameter. – Eljay Apr 24 '23 at 11:17
  • 1
    @molbdnilo And because passing by const ref can "invent" objects, it's highly discouraged when the address of the parameter might be taken and compared: pass by pointer to const when you deal with specific objects and might compare their addresses. – curiousguy Apr 27 '23 at 19:18
  • 1
    @Eljay Pass by scalar have a lot of rules allowing you to have the right overloaded selected if you have signatures `f(Base*)` and `f(Der*)` call `f` with a `Der2*` (the overload taking the most derived type is selected); UDT with user defined conversions will have so such preference, it will be ambiguous. – curiousguy Apr 27 '23 at 19:20

1 Answers1

5

The function that does this is std::shared_ptr's constructor, specifically overload 9:

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

These are the semantics:

Constructs a shared_ptr which shares ownership of the object managed by r. If r manages no object, *this manages no object either. 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*.

Important is the last sentence. Your Cat* (Y*) is convertible to / compatible with your Animal* (T*). So, this constructor participates in overload resolution and is selected because it is the only one available (the other overloads do not match).


Here is a simplified example that highlights how the general concept works, that std::shared_ptr uses in the quoted overload:

struct A{};

struct B {
  B(A const& a) {}
};

void foo(B const&) {}

int main() {
  A a;
  foo(a);
}

The call to foo implicitly creates a B object via B::B(A const&). foo is then passed a const reference to that temporary B object.

The fact that in your example B and A were types stemming from the same class template is immaterial.

bitmask
  • 32,434
  • 14
  • 99
  • 159