C++ 在父类(super class)构造函数中运行依赖于子类重写的大量变量的代码的正确方法是什么?

标签 c++ inheritance constructor

假设我有一个父类(super class),它在初始化时想要运行一些依赖于一大堆类变量的代码,这些变量可能会或可能不会被其构造函数中的子类覆盖。

公认的、干净的编码方式是什么?

我感觉我脑子在放屁;这应该是继承的标准初学者用法,但我无法弄清楚。

例如假设我有一个代表车辆的父类(super class),当它启动时,我想做一大堆代码来处理,比如说,每个轴的负载或其他东西(无关紧要),但该代码用作输入一堆所有车辆都存在的参数(因此存在于父类(super class)中),例如重量、长度、numwheels、numaxles,甚至可能是定义每轴有多少个车轮的复杂数据结构等)。

各种子类(跑车、bigrig、摩托车)想要在父类(super class)进行处理之前设置重量、长度、numwheels、numaxles 等。


Super::Super() {
    Process(var1_,var2_,var3_,var4_, ...);
}

Sub1::Sub1(): Super() {
    var1_ = <some math>;
    var2_ = <some math>;
    ...
}

不起作用,因为父类(super class) Process() 在子类设置变量之前运行。对吗?


Super::Super(float var1, WackyDatastructureDef var2, int var3, WackyStruct2 var4, ...),
var1_(var1), var2_(var2), var3_(var3), ............... {
    Process(var1_,var2_,var3_,var4_, ...);
}

Sub1::Sub1(): Super(<some math>, <some math>, <some math>, <some math>, ......) {
    ....
}

由于显而易见的原因,看起来很糟糕。另外,如果我只需要覆盖 20 个默认变量值中的 2 个,这看起来会很痛苦。


Super::Super() {}
void Super::Init() {
    Process(var1_, var2_, var3_, var4_ ...... );
}

Sub1::Sub1(): Super() {
    var1_ = <some math>;
    var2_ = <some math>;
    ...

    Init();
}

看起来最干净,但我不喜欢它......必须记住在所有子类构造函数的末尾调用 Init() 很奇怪。如果另一个程序员想要从我的父类(super class)中继承子类并且不知道我的魔法规则怎么办?


正确的做法是什么?

最佳答案

有很多方法可以解决这个问题(在 C++ 中为 lack of virtual constructors)。每一种都有其自身的优点和缺点。解决此限制的最常见模式:

  • 将所有必需的参数传递给基类构造函数。如果您需要多个参数,这可能会非常烦人。如果需求发生变化,代码的可读性将越来越差,并且很难扩展。当然它有一个很大的好处:它不是一种解决方法,每个人都会理解它。

  • 更改您的设计(这可能是最好的选择,但可能需要大量工作)。如果您需要很多参数,那么您可以将所有参数打包在单独的类中,它将保存对象状态。基类构造函数将只接受这种类型的一个参数,并且它将包含其状态(或仅包含其初始化数据,但这是另一回事了)。它的好处是保持设计清晰(没有像第一个解决方案那样的解决方法),但如果这个初始化 token 将随其自己的类层次结构发展,则可能会涉及一些复杂性。

  • 添加公共(public)初始化方法。将您的 Init() 方法更改为公共(public)方法,它不会由派生构造函数调用,而是由类用户调用。这将允许您在每个派生类中添加初始化代码(然后初始化顺序由实现本身控制)。这种方法非常老派,它要求用户调用它,但它有一个很大的好处:它是众所周知的,并且不会让任何人感到惊讶。请参阅this post here on SO关于它们的小讨论。

  • 虚拟构造函数习惯用法。请参阅this article供引用。它按预期工作,并且您可以通过一些模板方法使其更易于使用。 IMO 最大的缺点是它改变了创建新的派生类时管理继承和初始化的方式。这可能无聊并且容易出错并且冗长。此外,您还更改了类的实例化方式,对我来说,这总是很烦人。

关于第二个解决方案的一些注释(来自评论)。如果您应用此功能,我至少会看到以下选项:

  • 愚蠢的实体(只有数据,没有逻辑),包含所有必需的参数。

  • 将对象状态封装在单独的对象中。从派生类传递的对象不会被使用和删除,但它将成为对象的一部分。

在这两种情况下,您都可以有或没有参数的并行层次结构(BaseParametersHolderDerivedParametersHolder 等)。请注意,持有者不会遇到与第一个解决方案相同的问题(许多参数),因为创建可以委托(delegate)给私有(private)函数(示例是为了说明概念,代码远非):

class Derived : public Base 
{ 
public:
    Derived() : Base(CreateParameters()) 
    {
    }

private:
    ParameterHolder CreateParameters()
    {
        ParameterHolder parameters;
        parameters.Value = 1;
        parameters.AnotherValue = 2;

        return parameters;
    }
};

用什么?没有答案。我希望在代码之间保持一致(因此,如果您决定使用持有者,那么在任何地方都使用它们,不要混合 - 例如 - 与 v.i. 习语混合)。每次只选择合适的一个并尽量保持一致。

关于C++ 在父类(super class)构造函数中运行依赖于子类重写的大量变量的代码的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21108836/

相关文章:

c++ - 在不知道字符串宽度的情况下左右对齐

C++ - 生成子类实例的父类(super class)构造函数

c++ - 我有一个类对象作为另一个类的成员,如何初始化为安全的空状态?

c++ - 创建线程的简单错误

c++ - 为什么在此代码中调用析构函数?

javascript - 类继承 - 添加参数而不重写父构造函数参数

python - 使用父类实例创建子类对象

c++ - 采用不同大小数组的构造函数

c++ - 将字符串从 std::string 转换为 const char * 时出现数据丢失问题

C# 继承将一个子对象转换为另一个子对象