Search code examples
c++c++11shared-ptrsmart-pointersreinterpret-cast

C++ reinterpret_cast of std::shared_ptr reference to optimize


You have two classes Animal and Dog (where Dog inherits from Animal), and you have a situation where you are often expecting an animal but are sending an instance of a dog. In my particular case, I am often casting a strong pointer (std::shared_ptr<Dog>) to an animal-expecting function (std::shared_ptr<Animal>).

If we accept that we can make the function parameter a reference (std::shared_ptr<Animal>&, avoiding arguments as to why you should not have strong pointers as reference parameters because of concerns of changing ownership with threads), I assume we would be safe memory-wise to cast a std::shared_ptr<Dog> dog using reinterpret_cast<std::shared_ptr<Animal>&>(dog), right?

And if so, what could come up other than threading issues; such as that of the reference counting variety?

To be clear, the intent is to have a solution that would be used in many instances, where casting once isn't really a viable solution. It's more the issue that there are many objects that would have to be cast. Also, ignoring that std::unique_ptr may or may not be a better solution.

To add a final requirement - using plain pointers would not allow me to change the original std::shared_ptr in the case of a generalized serializer class function that is virtual and thus cannot be made templated.


Solution

  • I am often casting a strong pointer (std::shared_ptr<Dog>) to an animal-expecting function (std::shared_ptr<Animal>).

    There is no need to cast (i.e. explicitly convert). Shared pointer to derived type is implicitly convertible (1) to a shared pointer to base class.

    (a) If we accept that we can make the function parameter a reference

    We can only accept this assumption if a few requirements are met. Firstly, the function must not store the referenced argument beyond the scope of the function (except by copying). Secondly, the pointer passed as a reference must be a local or a temporary - or if isn't, then the function must not call any functions that could have directly or indirectly access to the referenced pointer.

    Also consider that if the reference is non-const, then you cannot rely on the implicit conversion (1). Instead, you must create a separate shared pointer of correct type (you can use implicit conversion to create it and pass that. A const reference argument, or a non-reference argument do not have that problem.

    (b) I assume we would be safe memory-wise to cast a std::shared_ptr dog using reinterpret_cast<std::shared_ptr<Animal>&>(dog), right?

    Standard-wise, your suggested cast is not safe - it has undefined behaviour. I don't see how assumption (b) follows from the assumption (a).


    If it's fine to use reference to shared_ptr as an argument in your case, but you cannot avoid increment/decrement of the refcounter due to conversion (derived to base, non-const to const), I recommend using a bare pointer for the argument instead.