5

I would like to create a new object on the heap based off another object that's type can only be known at runtime.

For a hypothetical example, say we have a game where the user picks a character which is either a wizard, warrior, or healer. The computer would create a matching non-player character to fight against the player. that is, if the player chose a warrior, the computer would generate another warrior instance.

I am trying to use polymorphism. Say wizard, warrior, and healer all inherit from the class "Combattype"

what I would like to do is something like the psuedocode:

    combattype* player = new (chosen at runtimetype)();//player
    combattype* baseptr = new typeid(*player);   // computer - this doesn't work

I know I may be able to write an if statment using something like

if(typeid(player).name(*player) == typeid.name(warrior)) { // make warrior}

but this would get complex very quickly with hundreds of types. I feel like there is a better way, but I cannot come up with it conceptually.

I'm trying to do something similar to this question, but in c++: Dynamically create an object of <Type> Thanks for any help.

Community
  • 1
  • 1
Enigma22134
  • 532
  • 5
  • 12
  • Instead of "trying to use polymorphism" (read as: this is your class assignment, and the professor insisted on polymorphism), why not use those mechanisms that are helpful and natural to you? – Bruce David Wilner Jul 14 '16 at 00:20
  • 1
    I'm not in a class. I'm learning cpp coming from a java background, so polymorphism is pretty natural to me. ;) I don't need to use polymorphism, but I thought that might be the right direction; just getting a type and instantiating a new object based on that type is fine with me. – Enigma22134 Jul 14 '16 at 00:34

1 Answers1

4

This (if I understand the question correctly) is known as ¹cloning. Just add a virtual clone member function in the base class. Override it in every concrete derived class.


Example of the core functionality:

class Base
{
private:
    // Whatever
public:
    virtual auto clone() const
        -> Base*
    { return new Base( *this ); }

    virtual ~Base() {}
};

class Derived_A
    : public Base
{
public:
    auto clone() const
        -> Derived_A*        // OK, covariant return type.
        override
    { return new Derived_A( *this ); }
};

#include <assert.h>
#include <typeinfo>
auto main()
    -> int
{
    Base const& o = Derived_A{};
    auto p = o.clone();
    assert( typeid( *p ) == typeid( Derived_A ) );
    delete p;       // ← Manual cleanup is a problem with basic cloning.
}

Overriding the clone function with a function that returns Derived_A* instead of general Base* is OK because it's a raw pointer and the result type is covariant (more specific in the more specific class, i.e. varies in the same way as the class specificity). It would also work nicely also for a raw reference. But C++ does not directly support this for class type function results, and that includes smart pointers as clone function results.

As the comment indicates, one problem with the direct, simple cloning is that the cleanup responsibility is unclear. It would be better to have that automated and guaranteed, but then one runs into the no-support-for-covariant-smart-pointers problem. Happily that covariance can be implemented “manually”, by making the virtual clone function non-public, and providing a class-specific smart pointer result wrapper function in every class.


Example of cloning with covariant smart pointer result:

#include <memory>       // std::unique_ptr

class Base
{
private:
    // Whatever, and
    virtual auto virtual_clone() const
        -> Base*
    { return new Base( *this ); }

public:
    auto clone() const
    { return std::unique_ptr<Base>( virtual_clone() ); }

    virtual ~Base() {}
};

class Derived_A
    : public Base
{
private:
    auto virtual_clone() const
        -> Derived_A*        // OK, covariant return type.
        override
    { return new Derived_A( *this ); }

public:
    auto clone() const
    { return std::unique_ptr<Derived_A>( virtual_clone() ); }
};

#include <assert.h>
#include <typeinfo>
auto main()
    -> int
{
    Base const& o = Derived_A{};
    auto p = o.clone();
    assert( typeid( *p ) == typeid( Derived_A ) );
    // Automatic cleanup.
}

Here is one way to automatically generate the cloning support machinery in Base and Derived_A, based on the notion of middle-man inheritance:

#include <memory>       // std::unique_ptr
#include <utility>      // std::forward

// Machinery:
template< class Derived_t >
class With_base_cloning_
{
private:
    auto virtual virtual_clone() const
        -> Derived_t*
    { return new Derived_t( *static_cast<Derived_t const*>( this ) ); }

public:
    auto clone() const
    { return std::unique_ptr<Derived_t>( virtual_clone() ); }

    virtual ~With_base_cloning_() {}
};

template< class Derived_t, class Base_t >
class With_cloning_
    : public Base_t
{
private:
    auto virtual_clone() const
        -> Base_t*          // Ungood type because Derived_t is incomplete here.
        override
    { return new Derived_t( *static_cast<Derived_t const*>( this ) ); }

public:
    auto clone() const
    { return std::unique_ptr<Derived_t>( static_cast<Derived_t*>( virtual_clone() ) ); }

    template< class... Args >
    With_cloning_( Args... args )
        : Base_t( std::forward<Args>( args )... )
    {}
};

And you'd use it like this:

// Usage example:

class My_base
    : public With_base_cloning_<My_base>
{};

class Derived_A
    : public With_cloning_<Derived_A, My_base>
{};

#include <assert.h>
#include <typeinfo>
auto main()
    -> int
{
    My_base const& o = Derived_A{};
    auto p = o.clone();
    assert( typeid( *p ) == typeid( Derived_A ) );
    // Automatic cleanup.
}

The return type of the (private) virtual_clone function in With_cloning_ is not directly what one desires, not ideal, because the derived class is not yet complete at the point where the template is instantiated, so the compiler doesn't yet know that it's derived from the template instantiation.

Alternatives to this middle-man inheritance solution, include a simple code generation macro, and (complex) dominance in a virtual inheritance hierarchy.


¹ A clone function, at bottom invoking the most derived class' copy constructor, is one special case of the virtual constructor idiom. Another special case is a create function, which at bottom invokes the most derived class' default constructor.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331