Home > Software design >  Avoiding struct downcasting when passing to a function (C )
Avoiding struct downcasting when passing to a function (C )

Time:11-21

I have a set of Arguments defined as struct for a set of operations (mean, minmax etc.)

struct Arguments {
    double *data;
    int num_data;

    Arguments(double *data, int num_data) : data(data), num_data(num_data) {}
};

struct MeanOperationArguments: Arguments {
    MeanOperationArguments(double *data, int num_data) : Arguments(data, num_data) {}
};

struct MinmaxOperationArguments: Arguments {
    bool is_min_op;
    MinmaxOperationArguments(double *data, int num_data, bool is_min_op) : is_min_op(is_min_op), Arguments(data, num_data) {}
};

I need to define an Operation class as follows:

class Operation {
public:
   virtual void execute() = 0;
}

class MeanOperation: public Operation {}

// an operation that can be told to display either the minimum or the maximum.
class MinmaxOperation: public Operation {}

Also, I have an operation factory with returns the specifc operation object instance based on the type of operation:

class OperationFactory {
public:
    Operation *get(OP_TYPE t, Arguments *args) {
        switch(t) {
            case MEAN:
                return new MeanOperation(args);
            case MINMAX:
                return args->is_min_op ? // ERROR: Because struct downcasts to `Arguments`
                    new MinOperation(args):
                    new MaxOperation(args);
        }
    }
};

I need to be able to run my operation based on the type of argument struct like this:

int main() {
    double data[] = { 1, 2, 3, 4 };
    int num_data = 4;
    
    OperationFactory operations;

    Arguments *mean_args = new MeanOperationArguments(data, num_data);
    Operation *mean_op = operations.get(MEAN, mean_args);

    mean_op->execute();

    Arguments *min_args = new MinmaxOperationArguments(data, num_data, true);
    Operation *min_op = operations.get(MINMAX, min_args);

    min_op->execute();

    return 0;
}

How can I initialize my operation with require arguments based on the use case?

CodePudding user response:

There are multiple things I have to address. First, avoid structure parent / child relationships. It adds unnecessary dependencies. Look at structures like custom data structures. Data is data at the end of the day. It only has meaning when you interpret it. Going off that logic, your argument structure could be simplified as an array with an unsigned integer that tells how long is that array (similar to a vector, so maybe you could look into using a vector instead of a struct). Going off this logic, the best approach you can take is having multiple functions with different names that take in the same arguments but return different result based on whatever it is that you want it to do. Here is what I am talking about:

#include <iostream>

struct DataSet {
    public:
        double* data;
        int size;
        
        DataSet(double* data, unsigned int size) {
            this->data = new double[size];
            this->size = size;

            for (unsigned int i = 0; i < size; i  )
                this->data[i] = data[i];
        }
};

double mean(const DataSet& dataSet) {
    double mean = 0;
    for (unsigned int i = 0; i < dataSet.size; i  )
        mean  = dataSet.data[i];
    mean = mean / dataSet.size;
    return mean;
}

double min(const DataSet& dataSet) {
    double min = dataSet.data[0];
    for (unsigned int i = 1; i < dataSet.size; i  )
        if (dataSet.data[i] < min)
            min = dataSet.data[i];
    return min;
}

double max(const DataSet& dataSet) {
    double min = dataSet.data[0];
    for (unsigned int i = 1; i < dataSet.size; i  )
        if (dataSet.data[i] > min)
            min = dataSet.data[i];
    return min;
}

int main() {
    double data[5] = { 1, 2, 3, 4, 5 };
    unsigned int size = 5;
    DataSet dataSet = DataSet(data, size);

    double result = 0;

    result = mean(dataSet);
    std::cout << "Mean: " << result << std::endl;

    result = min(dataSet);
    std::cout << "Min: " << result << std::endl;
    
    result = max(dataSet);
    std::cout << "Max: " << result << std::endl;
}

I included everything in one .cpp file for convenience. If you are trying to implement a system, I would suggest making an enum class, store an enum value that represents what operation the user wants to perform, make a switch statement that points to these functions.

Note, be careful with passing pointers around because you might end up with memory leaks. If you notice in the code implementation, I am doing a deep copy, therefore passing memory ownership to the structure to DataSet.

Edit for better system design fit

#include <iostream>

class DataSet {
    public:
        double* data;
        int size;
        
        DataSet() {
            data = nullptr;
            size = 0;
        }
        DataSet(double* data, unsigned int size) {
            this->data = new double[size];
            this->size = size;

            for (unsigned int i = 0; i < size; i  )
                this->data[i] = data[i];
        }
        ~DataSet() {
            if (data != nullptr)
                delete(data);
        }
};

class Operation {
    protected:
        DataSet dataSet;
    public:
        Operation(double* data, unsigned int size) : dataSet(data, size) {
            
        }

        virtual double execute() = 0;
};

class Mean : public Operation {
    public:
        Mean(double* data, unsigned int size) : Operation(data, size) {

        }
        ~Mean() {

        }

        double execute() {
            double mean = 0;
            for (unsigned int i = 0; i < dataSet.size; i  )
                mean  = dataSet.data[i];
            mean = mean / dataSet.size;
            return mean;
        }
};

class MinMax : public Operation {
    public:
        bool useMin;
        MinMax(double* data, unsigned int size) : useMin(true), Operation(data, size) {

        }
        ~MinMax() {

        }

        double execute() {
            if (useMin) {
                double min = dataSet.data[0];
                for (unsigned int i = 1; i < dataSet.size; i  )
                    if (dataSet.data[i] < min)
                        min = dataSet.data[i];
                return min;
            }
            else {
                double min = dataSet.data[0];
                for (unsigned int i = 1; i < dataSet.size; i  )
                    if (dataSet.data[i] > min)
                        min = dataSet.data[i];
                return min;
            }
        }
};

int main() {
    double data[5] = { 1, 2, 3, 4, 5 };
    unsigned int size = 5;
    DataSet dataSet = DataSet(data, size);

    double result = 0;

    Mean mean = Mean(data, size);
    std::cout << "Mean: " << mean.execute() << std::endl;

    MinMax minMax = MinMax(data, size);
    std::cout << "MinMax: " << minMax.execute() << std::endl;

    minMax.useMin = false;
    std::cout << "MinMax: " << minMax.execute() << std::endl;
}

For better fit your system, I worked out a better solution. I still got rid of your struct hierarchy but kept the hierarchy in your classes. MinMax will return min or max depending on the useMin boolean value. You said you are printing it in the comments, so you would just have to change it to void and instead of returning the value, just print it. I hope this points you into a better direction.

CodePudding user response:

If you put a single virtual method in the base class, preferably the destructor, you could use dynamic_cast to convert the pointer to an instance of the derived class. If the conversion fails you have your answer, if it succeeds you can call any of the derived class methods on it.

  • Related