47

I have a class hierarchy as follows:

class BaseSession : public boost::enable_shared_from_this<BaseSession>
class DerivedSessionA : public BaseSession
class DerivedSessionB : public BaseSession

Within the derived class functions, I regularly call functions like this:

Func(boost::dynamic_pointer_cast<DerivedSessionA>(shared_from_this()));

Since I was working with shared_ptr to manage the sessions, this was working fine. Recently, I discovered that my use of shared_ptr is not optimal for this case. That is because these sessions are singleton objects that maintain one socket per client. If socket is reconnected, the session copies used to become zombies.

As workaround, I started passing shared_ptr by reference rather than copies. This solved the zombie problem.

Ideally, I felt I should be using unique_ptr to store the session and then pass references to other functions. That opened a whole can of worms.

How do I cast a base class unique_ptr object to derived class unique_ptr object? What is the unique_ptr version of the following line?

Func(boost::dynamic_pointer_cast<DerivedSessionA>(shared_from_this()));

I just want one copy of the session object, everything else should be reference.

Sharath
  • 1,627
  • 2
  • 18
  • 34
  • What should happen with your pointed object if the dynamic cast fails? Should it be deleted or do you only expect a move if the cast succeeds and never a delete? – stefaanv Oct 15 '14 at 08:11
  • If dynamic_cast fails, the Func gets a null object and it cancels the operation. I don't want to move, the owner should remain same. – Sharath Oct 15 '14 at 08:46
  • I suspect I am looking for a weak_ptr to unique_ptr object. Short of going back to C pointer, is there any other choice? – Sharath Oct 15 '14 at 09:23
  • 1
    @SharathKShetty Short answer: no. `unique_ptr` implies ownership. Weak pointers require observation logic that implies locking and refcounting. That comes at a price, which is why you need to use `shared_ptr` if you're so inclined. The good news is you can use `std::dynamic_pointer_cast<>` with that too (_and_ `weak_ptr`). Everybody wins. – sehe Oct 15 '14 at 09:35
  • I agree with sehe. Creating a unique_ptr that point to a resource already owned by another smart pointer without moving the resource between the two containers is wrong. A weak_ptr doesn't has the ownership of the resource pointed but a weak reference: so any operation from the weak_ptr to the unique_ptr is simply infeasible or greatly discouraged – Stefano Buora Oct 15 '14 at 09:45
  • @StefanoBuora: The resource will be owned only by unqiue_ptr, I want to do away with shared_ptr. Looks like the solution provided by Jarod42 is the best option for me. – Sharath Oct 15 '14 at 09:58

4 Answers4

45

Update

The question has been clarified:

sorry I was not clear. I want the ownership to remain with original owner, the called function should only get reference to it, not ownership. Not looking for two smart pointer for the same object.

In that case, the solution is simply:

dynamic_cast<B&>(*my_unique_ptr)

Done. It throws if the cast doesn't succeed.


Casting shared_ptr

For shared_ptr there is std::dynamic_pointer_cast<> (http://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast)

Casting unique_ptr

The simplest way would seem:

#include <memory>

struct A { virtual ~A() = default; };
struct B : A { };

int main()
{
    std::unique_ptr<A> pa(new B);

    std::unique_ptr<B> pb(dynamic_cast<B*>(pa.release())); // DO NOT DO THIS
}

As the commenter rightfully points out, this may leak the object if the conversion failed. That's not very helpful.

A reason why the dynamic_unique_ptr_cast<> doesn't exist might be that the unique_ptr type doesn't erase the deleter. It could be hard/impossible to choose an appropriate delete for the target pointer type.

However, for simple cases, you could use something like this:

template <typename To, typename From, typename Deleter> 
    std::unique_ptr<To, Deleter> dynamic_unique_cast(std::unique_ptr<From, Deleter>&& p) {
        if (To* cast = dynamic_cast<To*>(p.get()))
        {
            std::unique_ptr<To, Deleter> result(cast, std::move(p.get_deleter()));
            p.release();
            return result;
        }
        return std::unique_ptr<To, Deleter>(nullptr); // or throw std::bad_cast() if you prefer
    }


auto pb = dynamic_unique_cast<B>(std::move(pa));
sehe
  • 374,641
  • 47
  • 450
  • 633
  • I want to avoid moving the ownership of the session object. I want to pass unique_ptr by reference after the dynamic cast. – Sharath Oct 15 '14 at 08:54
  • @SharathKShetty That makes zero sense. If ownership is to be shared, use shared_ptr or even a raw pointer (`dynamic_cast(p.get())`). Creating a two different smart pointers to the same object is **always a bad design smell** and asking for trouble. `unique_ptr`is precisely about transferring ownership. If you dont require it, use something else. – sehe Oct 15 '14 at 09:31
  • sorry I was not clear. I want the ownership to remain with original owner, the called function should only get reference to it, not ownership. Not looking for two smart pointer for the same object. I have mentioned that in the last line of my question. – Sharath Oct 15 '14 at 10:11
  • @SharathKShetty in that case: `dynamic_cast(*my_unique_ptr)` done. It throws if the cast doesn't succeed. – sehe Oct 15 '14 at 10:13
  • 1
    Thanks @sehe. That is a nice twist to Jarad42's solution. :-) – Sharath Oct 15 '14 at 10:23
  • @SharathKShetty I don't see how that's a twist. It's just doing what you want to do. Without needless complications. I've added it to my answer, just to be complete. – sehe Oct 15 '14 at 10:25
  • @Sharath I see why you say it's a twist - they're similar in intent. The difference is of course that (as said) casting the reference version will throw (`std::bad_cast`), but casting the pointer version won't (it can be null). I would argue both are appropriate, depending on how you want to handle a failed cast. – wardw Sep 15 '15 at 18:20
  • It worked in 2010, but this gives a compiler error C2440 in Visual Studio 2015. – Christopher Pisz Sep 18 '17 at 22:46
  • You're doing something wrong (guesses: [your type is not polymorphic at all](http://rextester.com/LRZTPM83624) or [the types are not related](http://rextester.com/NCYZX35443) or [a simple mistake](http://rextester.com/LRZTPM83624)?) – sehe Sep 19 '17 at 07:44
  • I never asked you to troubleshoot my problem. I fixed my problem by taking out your code and replacing it with [common sense](http://rextester.com/YMT2386). To test your snippet, I copied pasted your code in a fresh shiny project in vc140 and it did not compile. Error C2440 definitely applies if you go look up the error. – Christopher Pisz Sep 19 '17 at 16:44
  • 2
    Yeah, there's been a lot of noise coming from this post over the last two days. I tried to preserve value where possible the last time, but this discussion has gone on too long already and does not appear that it will bear fruit any time soon. Either take it to chat, or one of you can ask a new question about the problem and let a dispassionate 3rd party sort out the correct answer. – Cody Gray - on strike Sep 20 '17 at 18:25
  • 1
    I could only get this to work by removing the `Deleter` template parameter completely, since `default_delete` would not convert to `default_delete`. But that's the "simple case" I was interested in anyway. – Danra May 31 '20 at 14:09
  • 1
    Thanks @Danra this might actually shed light for others who ran into similar cases before, which I wasn't aware of. If you have a minimal example you can add it to the answer in case it helps – sehe May 31 '20 at 21:26
  • @sehe Do you have a working example of the above code as-is? If so can you please share it so I can see what I did differently? If not I suggest you just amend your answer and remove the `Deleter` template parameter completely, was still helpful for me and probably for 99% of other users looking to do this for the simple case. – Danra Jun 02 '20 at 08:08
  • @Sharath Sure. It's literally the code from the answer pasted together: http://coliru.stacked-crooked.com/a/ed344ba1e80d54d9 - I donot like making the code less precise unless I know what problem that's solving. I'm also willing to find out there are compiler issues/library implementation issues to avoid, but right now I can only say that the code with deleter is correct and strictly more generic. I'm certainly hoping to see the problem demonstrated. If you can modify the code such that it fails on your platform, I'd happy to know the platform that it fails on. – sehe Jun 02 '20 at 13:17
27

Unless you want to transfer ownership of your std::unique_ptr<T>, your function should take pointer or reference to T.

So signature of Func should be something like Func(DerivedSessionA*)

and then your call may look like:

std::unique_ptr<BaseSession> ptr; // Initialize it with correct value

Func(dynamic_cast<DerivedSessionA*>(ptr.get()));

Or as you seems to call it directly from a method in BaseSession:

Func(dynamic_cast<DerivedSessionA*>(this));
Jarod42
  • 203,559
  • 14
  • 181
  • 302
3

This is dynamic_pointer_cast of boost. The idea is quite simple(but ignore the deleter).

//dynamic_pointer_cast overload for std::unique_ptr
template<class T, class U> std::unique_ptr<T> dynamic_pointer_cast( std::unique_ptr<U> && r ) BOOST_SP_NOEXCEPT
{
    (void) dynamic_cast< T* >( static_cast< U* >( 0 ) );

    BOOST_STATIC_ASSERT_MSG( boost::has_virtual_destructor<T>::value, "The target of dynamic_pointer_cast must have a virtual destructor." );

    T * p = dynamic_cast<T*>( r.get() );
    if( p ) r.release();
    return std::unique_ptr<T>( p );
}
xinnjie
  • 672
  • 5
  • 12
-1

Simply get the stored pointer using the std::unique_ptr<>::get() method:

Func(dynamic_cast<DerivedSessionA*>(shared_from_this().get()))

that if shared_from_this() has that prototype:

std::unique_ptr<BaseSession>& shared_from_this();
Stefano Buora
  • 1,052
  • 6
  • 12