Item tempChosenWeapon = myInventory.chooseItem();
this is an Item
. Not a type descended from Item
. It is an Item
.
Values in C++ have known types.
cout << tempChosenWeapon.getName() << endl;
all good, but please stop using namespace std;
Item *chosenWeapon = &tempChosenWeapon;
This is a pointer to an Item
. I can prove it is not polymorphic, because it is a pointer to a instance of type Item
. The compiler can probably prove it to.
cout << chosenWeapon->getName() << endl;//THE CODE WORKS UP TO HERE
ok, this repeats the previous call.
Weapon *maybeWeapon = dynamic_cast<Weapon*>(chosenWeapon);
This deterministically returns nullptr
. chosenWeapon
is an Item*
that we know points to an Item
, and an Item
is not a Weapon
.
cout << maybeWeapon->getName() << endl;
this dereferences nullptr
.
There are a number of ways to handle polymorphism in C++. But you have to think about it.
First, do you want value semantics? Value sematnics means that a copy of something is a copy of it. Things don't refer to other things; they are those things.
You can do value semantics with polymorphic values, but it takes a bit of work. You write two classes; the value wrapper, and the internal pImpl
.
The internal pImpl
has a std::unique_ptr<Impl> Impl->clone() const
method, and the value wrapper calls it when you copy it.
You write your interface like this:
template<class D>
struct clonable {
std::unique_ptr<D> clone() const = 0;
};
struct ITarget;
struct IItem:clonable<IItem> {
virtual std::string get_name() const = 0;
virtual bool can_equip( ITarget const& ) const = 0;
~virtual IItem() {}
};
struct Target;
struct Item {
using Impl = IItem;
explicit operator bool() const { return (bool)pImpl; }
IItem* get_impl() { return pImpl.get(); }
IItem const* get_impl() const { return pImpl.get(); }
template<class D>
D copy_and_downcast() const& {
auto* ptr = dynamic_cast<typename D::Impl const*>( pImpl.get() );
if (!ptr) return {};
return D(ptr->clone());
}
template<class D>
D copy_and_downcast() && {
auto* ptr = dynamic_cast<typename D::Impl*>( pImpl.get() );
if (!ptr) return {};
pImpl.release();
return D(std::unique_ptr<typename D::Impl>(ptr));
}
std::string get_name() const {
if (!*this) return {};
return pImpl->get_name();
}
bool can_equip(Target const& target)const{
if (!*this) return false;
if (!target) return false;
return pImpl->can_equip( *target.get_impl() );
}
Item() = default;
Item(Item&&) = default;
Item& operator=(Item&&) = default;
Item(std::unique_ptr<IItem> o):pImpl(std::move(o)) {}
Item(Item const& o):
Item( o?Item(o.pImpl->clone()):Item{} )
{}
Item& operator=( Item const& o ) {
Item tmp(o);
std::swap(pImpl, tmp.pImpl);
return *this;
}
private:
std::unique_ptr<IItem> pImpl;
};
which probably has bugs and is maybe too complex for you.
Second, you can go with reference semantics.
In this case, you want to return shared_ptr<const T>
or shared_ptr<T>
from your data. Or you can go half way and return a unique_ptr<T>
copy from your chooseItem
functions.
Reference semantics is really hard to get right. But you do get to use dynamic_cast
or dynamic_pointer_cast
directly.
std::shared_ptr<Item> chosenWeapon = myInventory.chooseItem();
if (!chosenWeapon) return;
std::cout << chosenWeapon->getName() << std::endl;
auto maybeWeapon = dynamic_pointer_cast<Weapon>(chosenWeapon);
if (maybeWeapon)
std::cout << maybeWeapon->getName() << std::endl;
else
std::cout << "Not a weapon" << std::endl;