【问题标题】:Avoiding struct downcasting when passing to a function (C++)传递给函数时避免结构向下转换 (C++)
【发布时间】:2023-01-18 05:15:03
【问题描述】:

我有一组参数定义为一组操作的结构(均值、最小值等)

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) {}
};

我需要定义一个手术类如下:

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 {}

另外,我有一个操作工厂,它根据操作类型返回特定的操作对象实例:

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);
        }
    }
};

我需要能够基于参数结构的类型运行我的操作,如下所示:

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;
}

如何根据用例使用 require 参数初始化我的操作?

【问题讨论】:

  • 我可以帮忙,但是你需要这种结构层次结构和类的复杂结构吗?这是任何人或任何事物强制执行的事情吗?
  • 不是真的,这就是我想做的。但我对实施持开放态度。想要创建一个非常通用的结构来支持具有不同参数类型的更多操作
  • 如果您使用的是 args->is_min_op,那么 Arguments 必须有一个成员 is_min_op 而不是添加 virtual Operation* Arguments::GetOp(OP_TYPE t);
  • 这里的数据流似乎不对,从参数中获取操作不是很直观..
  • 恰恰相反,将操作与参数分开可能不是一个好主意。在这两行中Arguments *mean_args = new MeanOperationArguments(data, num_data); Operation *mean_op = operations.get(MEAN, mean_args);你是说你想要平均操作两次.重复自己会适得其反。说一次就足够了。

标签: c++ oop inheritance struct


【解决方案1】:

如果将单个虚方法放在基类中,最好是析构函数,则可以使用 dynamic_cast 将指针转换为派生类的实例。如果转换失败,您将得到答案,如果转换成功,您可以调用它的任何派生类方法。

【讨论】:

    【解决方案2】:

    有很多事情我必须解决。首先,避免结构父/子关系。它增加了不必要的依赖。查看自定义数据结构之类的结构。数据是一天结束时的数据。只有当你解释它时,它才有意义。脱离该逻辑,您的参数结构可以简化为一个数组,其中包含一个无符号整数,该整数表示该数组的长度(类似于向量,因此也许您可以考虑使用向量而不是结构)。脱离这个逻辑,您可以采用的最佳方法是使用多个具有不同名称的函数,这些函数接受相同的参数,但根据您希望它执行的操作返回不同的结果。这就是我所说的:

    #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;
    }
    

    为方便起见,我将所有内容都包含在一个 .cpp 文件中。如果你正在尝试实现一个系统,我建议创建一个枚举类,存储一个代表用户想要执行的操作的枚举值,创建一个指向这些函数的 switch 语句。

    请注意,传递指针时要小心,因为你可能会导致内存泄漏。如果您在代码实现中注意到,我正在执行深拷贝,因此将内存所有权传递给数据集的结构。

    编辑以更好地适应系统设计

    #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;
    }
    

    为了更好地适应您的系统,我制定了一个更好的解决方案。我仍然摆脱了您的结构层次结构,但将层次结构保留在您的类中。 MinMax 将根据 useMin 布尔值返回最小值或最大值。你说你在 cmets 中打印它,所以你只需要将它更改为 void 而不是返回值,只需打印它。我希望这能为您指明一个更好的方向。

    【讨论】:

    • 目前尚不清楚这与所问问题有何关系。
    • 这个问题更多地指向系统设计。原本复杂的实现最终会指向其他主题和其他语言特性作为解决方案。
    • 嗯,非常感谢您的回答,我实际上更喜欢面向对象的设计。无论如何,我们可以解决这个问题吗?
    • 是的,尝试使用我的结构版本,但实现一个具有 virtual execute(const DataSet&amp; dataSet) = 0; 函数的 Operation 类。在子类中实现该功能。根据您使用的对象,您将获得 minmeanmax 结果。我会把它添加到原来的帖子中。
    • 所以我必须将 min 和 max 组合成一个名为 minmax 的函数,可以告诉它找到 min/max (这是我努力嵌入到我现有系统中的地方)
    【解决方案3】:

    就像是:

        case MINMAX:
            return dynamic_cast<MinmaxOperationArguments*>(args)->is_min_op ?
                new MinOperation(args):
                new MaxOperation(args);
    }
    

    请注意,应在使用前检查转换结果,否则在参数不正确的情况下可能会崩溃。 必须启用 RTTI。

    【讨论】:

    • 如果 dynamic_cast 失败,您将使用 NULL 指针进行测试。这将如何解决?由于您已经编辑了承认需要支票的答案,为什么您的代码不包含支票?
    • 如前所述,它很可能会崩溃。
    • 实际上崩溃是最好的情况,因为它会立即告诉您有问题。在这种情况下,我认为它很有可能不会崩溃。
    • 很可能它根本不会发生,除非他用不正确的参数调用操作。但是验证输入超出了这个问题的范围。事实上,OP 更愿意询问是否完全避免沮丧,所以关于虚拟功能解决方案(在其他地方建议)。但是我决定添加我的,因为他可能只想避免使用 C 风格的案例。
    猜你喜欢
    • 2014-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多