c++castingunique-ptrdynamic-cast

Casting `std::unique_ptr`


Is there any problem in performing dynamic casting via the following function?

template<typename Base, typename Derived>
    requires std::is_convertible_v<Derived&, Base&> &&
             std::is_polymorphic_v<Base>
inline std::unique_ptr<Derived> cast_to(std::unique_ptr<Base>&& ptr)
{
    return std::unique_ptr<Derived>(dynamic_cast<Derived*>(ptr.release()));
}

Solution

  • Yes, your function can easily leak memory if the cast fails. Consider the following:

    struct Base
    {
        virtual ~Base() = default;
    };
    
    struct Derived1 : Base {};
    struct Derived2 : Base {};
    
    int main()
    {
        std::unique_ptr<Base> bp = std::make_unique<Derived1>();
    
        // bp does not point to a Derived2, so this cast will fail
        auto d2p = cast_to<Base, Derived2>(std::move(bp));
        std::cout << bp.get() << '\n';  // 0
        std::cout << d2p.get() << '\n'; // also 0; object was leaked
    }
    

    Demo

    From this snippet you can also see another small issue: because of the order of the template parameters you have to supply them both. You can't let the compiler deduce Base because it comes before Derived.


    With both of those issues in mind, the following would be a better implementation:

    template<typename Derived, typename Base>  // swap the order of template parameters
        requires std::is_convertible_v<Derived&, Base&> &&
                 std::is_polymorphic_v<Base>
    inline std::unique_ptr<Derived> cast_to(std::unique_ptr<Base>&& ptr)
    {
        Derived* d = dynamic_cast<Derived*>(ptr.get());
        if (d) {
            ptr.release();
            return std::unique_ptr<Derived>(d);
        }
        return nullptr;  // object is still owned by ptr
    }
    

    This fixes both of the above issues:

    int main()
    {
        std::unique_ptr<Base> bp = std::make_unique<Derived1>();
    
        // No need to explicitly specify Base; the compiler can deduce that itself
        auto d2p = cast_to<Derived2>(std::move(bp));
        std::cout << bp.get() << '\n';  // not 0; no leak
        std::cout << d2p.get() << '\n'; // 0
    }
    

    Demo