Polymorphism: is this (potentially intensive) use of static_cast a fatality?

113 views Asked by At

Here is the context: A Model has a (pointer to) Parameter and an output. Model and Parameter are abstract classes. We use pointers of type Model* to manipulate various derived (concrete) classes of Model, whose pointers to Parameter dynamically point to instances of various derived (concrete) classes of Parameter.

Here is a simplified version of the classes as an example. I know new should be avoided or at least followed by delete, but I omitted off-topic lines of code (such as destructors).

// Abstract classes
class Parameter {
public:
    virtual void reinitialize() = 0;
};

class Model{
public:
    Model(Parameter *argt){ ptr_param = argt; }
    virtual void computeModelOutput() = 0;
    double output;
    Parameter *ptr_param;
};

// Concrete classes
class ACertainKindOfParameter : public Parameter{
public:
    ACertainKindOfParameter(int argt){ value = argt; }
    virtual void reinitialize(){ value = 1; }
    int value;
};

class ACertainKindOfModel : public Model{
public:
    ACertainKindOfModel(int argt) : Model(new ACertainKindOfParameter(argt)){}
    virtual void computeModelOutput(){
        output = 10.0 + (double)(static_cast<ACertainKindOfParameter*>(ptr_param)->value);
    }
};

int main(){
    ACertainKindOfModel myModel{5};
    Model *ptr_model = &myModel;
    ptr_model->computeModelOutput();
    std::cout << ptr_model->output << std::endl; // 15
}

What bothers me in this code is that ACertainKindOfModel has no direct access to value, so I apparently need to use static_cast. A real Model would of course have a vector of e.g. 50 Parameters, not just one, so that would mean 50 static_cast each time the output is computed (or any other action relying on parameters). That does not look like a good practice to me, but I may be wrong. Do you see any flaw in the design?

Note: I thought of making Parameter a class template, but it doesn't seem to be a valid option because the methods of Parameter differ deeply when different types of value are considered. In the simple example above, value is of type int, but in another class derived from Parameter it could be of user-defined type, e.g. Color with only three possible values R, G and B, and reinitialize() would be very different than value = 1. A virtual getter() in Parameter would be great but would not work either, because of a conflicting return type in the redefinition.

1

There are 1 answers

1
1201ProgramAlarm On BEST ANSWER

There are several approaches to make this cleaner. If Model does not need access to ptr_param, you could remove that from Model and store it within each derived class, with the correct type.

Or you can encapsulate the static_cast in a getter function within each model class:

ACertainKindOfParameter *getParam() const { return static_cast<ACertainKindOfParameter *>(ptr_param); }

You can combine the two techniques. Define the parameter within the derived model class, and use a covariant return type to allow the base Model class access. Within Model, declare a getter:

virtual Parameter *getParam() const = 0;

Then, within each model, declare a covariant override:

virtual ACertainKindOfParameter *getParam() const override { return ptr_param; }

which assumes ptr_param is declared within ACertainKindOfModel. If it isn't you'll need to apply the static_cast as above.

Or you can save the result of the static_cast within the compute function to avoid having to use it multiple times.