【问题标题】:Can a derived class be smaller than its parent class?派生类可以小于其父类吗?
【发布时间】:2020-08-23 08:13:53
【问题描述】:

我问这个关于 C++ 的问题是因为我很熟悉这个问题,但问题本身就很容易解释:是否有一种语言可以派生一个类并使其占用的内存空间比原始类少?

这个问题更像是一个噱头,而不是我试图解决的实际问题,但我可以想象一些真正高性能的代码可以从这种内存优化中受益:

假设我们有:

class Person {
    std::string name;
    unsigned int age;
}

class PersonNamedJared {
}

理论上,我们在这个子类中不需要字段“name”,因为它总是“Jared”,这样可以提高内存效率。

是否只有通过将Person 中的“名称”字段替换为get_name() 函数来实现这一点,我们只需在PersonNamedJared 中覆盖该函数以始终返回“Jared”?我们如何仍然在基类中设置 name 变量?

我知道这个例子是非常糟糕的做法,但这是我能想到的最好的例子。我认为实施这种模式是有正当理由的。

【问题讨论】:

  • afaik,派生类应该用于对 is-a 关系建模,所以我猜派生类需要至少与其基类一样大。
  • 有可能发明这样的语言,但没有理由这样做。始终可以将 所有 数据成员放在叶类中,这样您就不会继承任何不需要的数据。

标签: c++ inheritance derived-class memory-efficient


【解决方案1】:

派生类可以小于其父类吗?

没有。派生类总是包含一个基类子对象。对象永远不能小于其子对象,因此派生类永远不能小于其基类。

通过将 Person 中的“name”字段替换为我们在 PersonNamedJared 中简单地覆盖以始终返回“Jared”的 get_name() 函数来实现这一点的唯一方法是什么?

这将是实现它的一种方法。

我们如何仍然在基类中创建 name 变量?

您不能在派生类中包含不想出现在基类中的成员变量。

例如,您可以使用多重继承,这样您就可以在某些派生类中拥有带有变量的基础。像这样的:

struct Person {
    unsigned int age;
    virtual std::string name() = 0;
    ...

struct Named {
    std::string name;
};

struct NamedPerson : Person, private Named {
    std::string name() override {
        return name;
    }
};

struct JaredPerson : Person {
    std::string name() override {
        return "Jared";
    }
};

这里有一个带有变量的基,但 Jared 没有继承那个特定的基。

【讨论】:

  • 这似乎是不必要的复杂。我刚刚创建了 Person、NamedPerson 和 JaredPerson,没有多重继承。如果我想变得非常花哨,我会创建一个 FixedNamePerson 模板,该模板在编译时采用该名称。 PS:你真的应该在你的基类中有一个虚拟析构函数,并向我们经验不足的读者解释为什么这是一个好主意,不,一个要求!
  • 复杂性是有用还是不必要取决于实际用例。如果没有用例,就无法衡量这一点。在一种情况下,更简单的方法可能更好,而在另一种情况下,复杂的方法可能更好。对于引入其自身复杂性的模板建议也是如此。虚拟析构函数可能是一个好主意,但在我看来,解释它超出了这个问题的范围。我选择删除右大括号并添加省略号,这样不完整的基础就不会被逐字复制。
【解决方案2】:

没有。

从一个类继承意味着你包括它的所有成员变量、它的父类[s]成员变量,以及你自己的类所包含的任何东西。

换句话说,子类是父类的超集。 (空子类除外)

即使私有成员仍然那里占用空间,尝试访问它们只是编译器错误。

【讨论】:

    【解决方案3】:

    派生类可以小于其父类吗?

    在 C++ 中,不!

    即使您尝试用派生类中的较小成员替换基类的成员(使用相同的名称),基类成员仍然存在(只是更难访问)。这段代码将证明,派生类实际上添加到基类的大小:

    #include <iostream>
    
    class A {
    public:
        double q[200];
    };
    
    class B : A {
    public:
        double q[100]; // Can we replace base array with a smaller one?
    };
    
    int main()
    {
        std::cout << sizeof(A) << std::endl; // -> 1600 = 200 * sizeof(double)
        std::cout << sizeof(B) << std::endl; // -> 2400 ADDS 100 more doubles!
        return 0;
    }
    

    【讨论】:

      【解决方案4】:

      如果使用多态性,答案应该很清楚。也就是说,利用允许您将派生类型的对象视为基类型之一的语言特性。假设PersonPersonNamedJared 的公共基并考虑以下代码。

      PersonNamedJared Jared;
      Person * Pointer = &Jared;
      std::cout << Pointer->name << " is " << Pointer->age << " years old.";
      

      这是有效的,但如果 PersonNamedJared 缺少 name 字段,这段代码怎么可能工作?

      根据经验,如果您有正当理由希望派生类小于其基类,那么您的类设计可能有问题。


      关于是否有一种语言具有此功能,我认为这是可能的,但比较尴尬。您可以——用一种假设的语言——模糊数据成员和函数成员之间的界限,允许标识符name 表示基类中的(虚拟)数据但派生类中的函数。因此派生类可以提供一个名为name 的函数,覆盖基类版本。在处理派生类型的对象时,您可以阻止对name 的基类版本的显式引用。如果有足够的要求和禁令,name 字段在某些派生类对象中将无法访问,因此它们将不需要它。在这种情况下,可以省略名称字段。 (但是,我不知道有任何语言允许这种严格的设置。)

      另一方面,这种设置感觉与继承原则背道而驰。即使一种语言允许这种设置,我也更喜欢更好的类设计。

      【讨论】:

      • 实际上,Dart (by google) 基本上允许您使用类似变量的表示法运行自定义的“getter”和“setter”代码(例如,person.name 可能正在调用只返回“Jared”的自定义 getter ”)。正是从这里,我最初问自己如何实现这一点,因为现在我了解到继承只是编译器生成的组合,子类永远不能小于超类
      • @AdriaanJacobs 有一些语言在实际调用函数时使用类似变量的符号来模糊界限。不过,这只是其中的一部分。
      猜你喜欢
      • 2014-12-05
      • 1970-01-01
      • 2021-02-28
      • 1970-01-01
      • 2011-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-28
      相关资源
      最近更新 更多