Joseph Zambrano
Joseph Zambrano

Reputation: 133

C++ Array of Derived Classes

I am currently working on a problem that requires a few concepts to be tied together. I first define a class "Base" which uses the CRTP (see https://www.modernescpp.com/index.php/c-is-still-lazy) to achieve run-time polymorphism. I then define two derived classes of Base called "Derived1" and "Derived2" as examples of how this polymorphism will be used. This all works as expected until another concept of "Agent" is introduced. Agent will have various state variables as well as a pointer to its corresponding derived class. The goal is to have many such agents with potentially different derived classes. I'd like to store all of these objects in the same array. However, this seems to be ill-defined, as each Agent is not necessarily of the same type.

I have considered replacing the Base pointer in the Agent definition with an enum, and then retrieving the appropriate Derived class via a map. This does not feel particularly elegant. I also suspect there is a way to do this with virtual base classes, but I am trying to avoid the use of vtables for performance reasons.

Any advice or suggestions regarding a potential solution for my first proposed implementation, or a restructuring to achieve the intended goal would be greatly appreciated. Thank you!

#include <iostream>

template <typename T>
class Base {
public:
    void func() {
        static_cast<T*>(this)->implementation();
    }
};

struct Derived1: Base<Derived1> {
    void implementation() {
        std::cout << "calling derived1 implementation of func" << std::endl;
    }
};

struct Derived2: Base<Derived2> {
    void implementation() {
        std::cout << "calling derived2 implementation of func" << std::endl;
    }
};

class Agent {
    public:
        // various member variables to keep state
        Base *base;    // NOTE: this is where I am unsure
}

int main() {
    Agent agent_array[2];    // NOTE: this is ill-defined since I don't pass template args
    Derived1 d1;
    Derived2 d2;

}

Upvotes: 3

Views: 498

Answers (1)

Joel Filho
Joel Filho

Reputation: 1300

CRTP is used for static polymorphism.

If you need runtime polymorphism, there are many ways of achieving that. Virtual member functions with classic OOP inheritance is only one way.

From the excellent "Embrace no Paradigm Programming!", we can see other ways we can do it and how they compare to each other:

Performance between multiple ways of C++ runtime polymorphism

If you're only using a known number of derived classes, I'd recommend using a variant-like data structure. It has static storage, so no unnecessary heap allocations, and all of the storage is abstracted from you.

If you need to export the func() implementation explicitly, then you'd need to use polymorphic values ("type erasure" on the slides). See the classic Sean Parent talk "Better Code: Runtime Polymorphism". Notice it also uses vtables. To abstract vtables, you'd need to use a more complex struture, like Sy Brand shows in their "Dynamic Polymorphism with Metaclasses and Code Injection" talk.

Also, notice the "OO" performance is not that bad. Virtual member functions aren't that expensive in modern computers, and usually only high-performance/low-latency code cares deeply about this type of optimization.

Upvotes: 4

Related Questions