Reputation: 1206
If you have a class Base with virtual methods and a class Implementation which implements the virtual methods, is there any way to cast std::shared_ptr < Implementation > & to std::shared < Base > &? The compiler allows this for const references, but for non const references it fails as in "Case A" in the code below. Is there an easy way to do this?
If not, how safe is my workaround "questionable_cast" in Case B?
#include <iostream>
#include <memory>
class Base
{
public:
virtual void set_value(int x) = 0;
};
class Implementation : public Base
{
public:
Implementation() : m_value(0) { }
void set_value(int x) override
{
m_value = x;
}
int get_value() const
{
return m_value;
}
private:
int m_value;
};
void do_something(std::shared_ptr<Base>& base)
{
base->set_value(5);
/// Code like this makes the non-const argument necessary
base = std::make_shared<Implementation>();
}
template <class T, class U>
std::shared_ptr<T>& questionable_cast(std::shared_ptr<U>& u)
{
/// This code is here to assure the cast is allowed
std::shared_ptr<T> tmp = u;
(void)tmp;
return *reinterpret_cast<std::shared_ptr<T>*>(&u);
}
int main()
{
std::shared_ptr<Implementation> a = std::make_shared<Implementation>();
// The following line causes a compiler error:
// invalid initialization of reference of type ‘std::shared_ptr<Base>&’ ...
// do_something(a);
// do_something(std::dynamic_pointer_cast<Base>(a));
// This is the workaround
do_something(questionable_cast<Base>(a));
std::cerr << "a = " << a->get_value() << std::endl;
return 0;
}
Upvotes: 0
Views: 434
Reputation: 28987
Two obvious solutions to the problem as originally asked: 1. Make do_something
take a const reference to a shared_ptr (or a shared_ptr by value). 2. Create a named shared_ptr and pass a reference to that: Eg
int main()
{
std::shared_ptr<Implementation> a = std::make_shared<Implementation>();
std::shared_ptr<Base> b = a; // This conversion works.
do_something(b); // Pass a reference to b instead.
return 0;
}
Your questionable_cast
function is a violation of the strict aliasing rules, and invokes undefined behaviour. It's quite likely to work in initial tests, and then a new release of the compiler will crank up the optimization a notch, and it will fail during a demo.
To handle the case where do_something
changes the pointer:
int main()
{
std::shared_ptr<Implementation> a = std::make_shared<Implementation>();
std::shared_ptr<Base> b = a; // This conversion works.
do_something(b); // Pass a reference to b instead.
const auto aa = std::dynamic_pointer_cast<Implementation>(b);
if (aa)
a = aa;
else
; // Handle the error here
return 0;
}
If do_something
guarantees to return a pointer of the same derived type, even if it doesn't return the same pointer, wrap it in a template function:
template <typename T>
void do_something_ex( std::shared_ptr<T>& a )
{
std::shared_ptr<Base> b = a;
do_something(b)
a = std::dynamic_pointer_cast<T>(b);
if (!a)
throw_or_assert;
}
Upvotes: 2