Home > Software design >  Why does a dynamic_cast of a reference fail to compile if the base class has a unique_ptr?
Why does a dynamic_cast of a reference fail to compile if the base class has a unique_ptr?

Time:01-14

I ran into this problem below and am looking for understanding. In my application I have a base class with a pure virtual that takes a reference to the base class (think: to make a copy). In the derived class I cast the reference to the derived class. This works fine until I add a unique_ptr of the base class to the base class. If I change the cast from a reference to a pointer all is well, but I don't get it. Sample code:

#include <memory>
#include <iostream>

class Base {
public:
    Base() {}
    void hello() const { std::cout << "hello world" << std::endl; }
protected:
    virtual void cvt( Base& ) const = 0;
    std::unique_ptr<Base> any_member_unique_ptr;                    // "A"
};

class Derived : public Base {
public:
    Derived() { std::cout << "new Derived" << std::endl; }
    void cvt( Base& in ) const override {
        const auto cast_fail = dynamic_cast<const Derived&>( in );  // "B"
        const auto cast_okay = dynamic_cast<const Derived*>( &in );
        hello();
    }
};

int main(int argc, char *argv[])
{
    Derived D, P;
    D.cvt( P );
}

If the line commented as "A" or the line commented as "B" is commented out, the program compiles and runs fine (note that the member at "A" is never used). However, with both lines present g 11.3.0 reports:

tt.cpp: In member function ‘virtual void Derived::cvt(Base&) const’:
tt.cpp:17:69: error: use of deleted function ‘Derived::Derived(const Derived&)’
   17 |      const auto cast_fail = dynamic_cast<const Derived&>( in );  // "B"
      |                                                              ^

tt.cpp:13:7: note: ‘Derived::Derived(const Derived&)’ is implicitly deleted because the default definition would be ill-formed:
   13 | class Derived : public Base {
      |       ^~~~~~~
tt.cpp:13:7: error: use of deleted function ‘Base::Base(const Base&)’
tt.cpp:4:7: note: ‘Base::Base(const Base&)’ is implicitly deleted because the default definition would be ill-formed:
    4 | class Base {
      |       ^~~~
tt.cpp:4:7: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Base; _Dp = std::default_delete<Base>]’
In file included from /usr/include/c  /11/memory:76,
                 from tt.cpp:1:
/usr/include/c  /11/bits/unique_ptr.h:468:7: note: declared here
  468 |       unique_ptr(const unique_ptr&) = delete;
      |       ^~~~~~~~~~
make: *** [<builtin>: tt] Error 1

If I guru meditate on it long enough I think I can make out what it's saying (the copy-by-reference constructor for the unique_ptr is deleted) but I still don't get why casting a reference to the object would be different from casting a pointer to it.

Is there a general rule or idiom for this? I can "make it work" by just using the pointer, but I prefer references when possible.

CodePudding user response:

The reason the reference fails is because it is not included with the auto type

const auto cast_fail = dynamic_cast<const Derived&>( in );
const auto cast_okay = dynamic_cast<const Derived*>( &in );

written explicitly this is

const Derived cast_fail = dynamic_cast<const Derived&>( in );
const Derived* cast_okay = dynamic_cast<const Derived*>( &in );

note in the reference case this is casting to const Derived& but then assigning to a value, which will attempt a copy, and therefore fail since the class is non-copyable due to having a std::unique_ptr member which is non-copyable.

Either of these would work

const auto& cast_fail = dynamic_cast<const Derived&>( in );
const Derived& cast_fail = dynamic_cast<const Derived&>( in );

CodePudding user response:

const auto cast_fail = dynamic_cast<const Derived&>( in );  // "B"

This line casts to Derived& and then makes a copy. You can't copy unique_ptr (that's what unique means) so you also can't copy anything that has a unique_ptr in it.

But if you change it to const auto& cast_fail, then cast_fail will be a reference to a Derived, instead of a separate Derived object, and it will work.

cast_okay works because cast_okay is a pointer and nothing is copied.

  •  Tags:  
  • c
  • Related