Home > Back-end >  C How can I allocate dynamic memory for a polymorphic type depending on user input?
C How can I allocate dynamic memory for a polymorphic type depending on user input?

Time:11-10

Here's a few lines from my code:

class Base;
class DerivedA : public Base;
class DerivedB : public Base;
class DerivedC : public Base;

std::vector<Base*> vector_of_base;
vector_of_base.push_back(new Base);
vector_of_base.push_back(new DerivedA);
vector_of_base.push_back(new DerivedB);
vector_of_base.push_back(new DerivedC);

// input from the user
std::vector<std::size_t> user_defined_index;

std::vector<Base*> new_vector;
for(auto index: user_defined_index)
{
    // I have no idea if this will work as I expect
    Base *object = nullptr;
    object = vector_of_base.at(index);

    // compare the type of object to allocate the right thing
    if(typeid(object) == typeid(Base)
    {
        // allocate a new instance of Base
        // copying values from the previous instance (although
        // this is strictly speaking not necessary in my requirements)
        new_vector.push_back(new Base{object});
    }
    else if(typeid(object) == typeid(DerivedA))
    {
        new_vector.push_back(new DerivedA{object}); // ?
    }
    // etc ...
}

There is one immediate problem is that I have a significant number of Derived* types. (Somewhere around 20 - 30, and hence this produces a lot of duplicate code in one long if-else if-else statement.

However, I am not actually sure if the above code will actually work. Will the typeid(object) will always produce the value typeid(Base), or will it produce the "expected" value. In other words, if object was allocated using new DerivedA then does the typeid(object) statement produce the same value as typid(DerivedA)?

The code above might not be that clear, so here is a summary of what it does:

  • A vector is allocated with every possible type of Base and Derived type. Each of these types overloads a GetName() operator which returns a std::string.
  • These strings are inserted into a graphical user interface. The user selects the quantity of each type required. (May be zero.)
  • A new vector must be created, and it must contain objects allocated with new. The type of each object to be allocated is dictated by the users selection of quantity and order of each type from the GUI.

Hopefully that makes at least some sense?

CodePudding user response:

If you only want to shorten your code, you probably want this:

#include <cassert>
#include <iostream>
#include <vector>

using namespace std;

class A {
  public:
    virtual void print() const { cout << "A" << endl; }
};

class B : public A {
  public:
    virtual void print() const override { cout << "B" << endl; }
};

vector<A *(*)()> vec;

template <typename T> void foo() {
    vec.push_back([]() -> A * { return new T; });
}

int main() {
    foo<A>();
    foo<B>();
    // Add more foo<T>()

    int i = 0;
    vec[i]()->print(); // prints A
    i = 1;
    vec[i]()->print(); // prints B

    return 0;
}

CodePudding user response:

The OP doesn't specify if they can modify the class code, or whether they have to use the Base and Derived classes as given. IF the OP is in control, this is a basic clone idiom:

#include <iostream>
#include <memory>
#include <vector>
#include <string>

#define CLONE_CODE(T) \
std::unique_ptr<Base> clone() const { return std::make_unique<T>(*this); } 

class Base
{
public:
    virtual CLONE_CODE(Base)
    virtual std::string type() const { return "Base"; };     
    virtual ~Base() {}
};

class DerivedA : public Base
{
public:  
    CLONE_CODE(DerivedA)
    std::string type() const { return "DerivedA"; }
};

class DerivedB : public Base
{
public:
    CLONE_CODE(DerivedB)
    std::string type() const { return "DerivedB"; }
};
   
int main()
{
    using BasePtr = std::unique_ptr<Base>;
    using PtrVec = std::vector<BasePtr>;

    PtrVec vecSource;

    vecSource.push_back(std::make_unique<Base>());
    vecSource.push_back(std::make_unique<DerivedA>());
    vecSource.push_back(std::make_unique<DerivedB>());
    //etc

    std::vector<std::size_t> vecIndex = { 0,1,2,1,1 };

    PtrVec vecCopy;
    for (auto n: vecIndex)
         vecCopy.push_back(vecSource[n]->clone());
   
    //Check copied vector has the correct types
    for (auto& p : vecCopy)
        std::cout << p->type() << std::endl;
}

With the output:

Base
DerivedA
DerivedB
DerivedA
DerivedA

The code uses a macro which isn't great. Of course you can write the code long-hand for each derived class. There are template versions of this, involving an intermediate template class eg Forwarding Constructor with CRTP, but they get rather complex (for me anyway!).

NB.

  • I've used std::unique_ptr to wrap the raw pointers.
  • Assumes each class has a copy constructor available.
  • Related