【问题标题】:Why does the size of a derived class include private members from the base class?为什么派生类的大小包括来自基类的私有成员?
【发布时间】:2011-07-04 07:47:13
【问题描述】:

我有以下代码:

class A {
  private:
    int i;
 };

class B : public A {
 private:
  int j;
 };

当我检查sizeof(B) 时,它似乎是sizeof(base) + sizeof(derived)。但是,我对继承的理解是基类的private 成员不会被继承。那么为什么它们会包含在sizeof(B) 的结果中?

【问题讨论】:

标签: c++ inheritance


【解决方案1】:

所有成员变量都是继承的。 private protected public 修饰符只改变谁有权访问这些变量

【讨论】:

    【解决方案2】:

    你误解了private 的作用。在您的代码 sn-p 中,它只是阻止A 的非成员和非朋友访问i。它只是一个 access-control 修饰符。 B 的实例将具有 A 的数据成员,即使 B 无法(直接)访问它。

    一个类比表明这确实是有道理的:

    class Human
    {
    protected:
        void UseCognitivePowers() { brain.Process(); }
    private:
        Brain brain;
        // ...
    };
    
    class StackOverflowUserInSilico : public Human
    {
    private:
        void AnswerStackOverflowQuestion(int questionId)
        {
            // ...
            // magic occurs here
            // ...
            UseCognitivePowers();
        }
    };
    

    即使brainHuman 类中是私有的,StackOverflowUserInSilico 也会有一个Brain,因为StackOverflowUserInSilico 派生自Human。这是必需的,否则即使StackOverflowUserInSilico 继承了Human 的方法,UseCognitivePowers() 函数也不会工作。

    当然,Human 的子类是否会真正利用Human 类提供给它们的UseCognitivePowers() 方法是完全不同的事情。

    【讨论】:

    • @David:那是因为论证是错误的。正确的论点是:X 是人,人有大脑,因此 X 有大脑,这是完全合理的。
    【解决方案3】:

    您要么误解了sizeof,要么误解了 C++ 对象的布局(在内存中)。

    出于性能原因(避免间接成本),编译器通常会使用组合来实现派生:

    // A
    +---+
    | i |
    +---+
    
    // B
    +---+---+
    | A | j |
    +---+---+
    

    请注意,如果privateB 不能窥视A,即使它包含它。

    sizeof 运算符将返回B 的大小,包括必要的填充(用于对齐校正)(如果有)。

    如果您想了解更多信息,我衷心推荐 Stanley A. LippmanInside the C++ Object Model。虽然依赖于编译器,但实际上许多编译器确实使用相同的基本原则。

    【讨论】:

    • 如果没有这样做,究竟如何实现呢? (Vis 间接成本?)
    • @Billy:您可以决定通过指针嵌入基类(由new 创建并由delete 删除)。然后基类将始终占用 4 或 8 个字节(取决于系统)。这在 ABI 方面会更好一些,但成本更高。
    • M.:实际上,你不能那样做。如果它是作为自动变量创建的,那么它必须存在于自动存储中。使用new 之类的东西会将其放入动态存储中。 (此外,如果您只有一个基类指针,您将无法返回对象的派生部分——基类不知道它们的派生者)
    • @Billy:自动变量并不意味着stack,它意味着内存是为你管理的。从基础到派生的好点,但你忘记了它已经解决了虚拟继承(尽管它更昂贵)。
    • 大多数继承不是虚拟的。我看不出这如何解决任何问题。
    【解决方案4】:

    它是继承的 - 派生类对象将包含它,但它不能被派生类的成员函数访问。

    【讨论】:

    • 嘿..非常感谢..所以派生类继承了一切,但是派生类的公共成员函数无法访问基类ri8的私有成员!
    【解决方案5】:

    这已经在其他几个答案中得到了回答:访问说明符限制访问,但是类的成员属性仍然被继承。

    我只是想提供一个理由,因为当我看到其中的原因时,我通常会学得更好。基本上,当您从一个类型继承时,派生类型包含基类型的子对象,与基类型一样小或大。 需要所有成员变量的原因是派生对象一个基础对象,可以在其上调用基础级别的成员函数。即使派生类型不能访问私有成员属性,可以在该对象上调用的基方法可能仍然需要访问它,因此成员必须存在:

    class base {
       int x;
    public:
       base() : x(0) {}
    // ...
       void printout() {
          std::cout << x << std::endl;
       }
    };
    class derived : public base {
    // ... assume that derived does not hide `printout`
    };
    int main() {
       derived d;
       d.printout();  // this requires access to d.base::x
    }
    

    这只是一个简单的例子,你可以在这里说几句话来证明在某些情况下x 可以不需要(我们正在覆盖/隐藏printout在派生对象中...)但该语言仍然允许您通过限定访问隐藏/覆盖的成员方法,因此d.base::printout() 仍将在基本级别访问printout,而这又需要x

    【讨论】:

      【解决方案6】:

      访问说明符(公共/私有/受保护)无论如何都不会影响继承的“对象大小”。

      【讨论】:

      • 确实如此。它们控制类的哪些成员将在驱动类中可见。
      • @iammilind:它们也会影响大小。由于一个神秘的规则规定它们会影响类的布局,因此更改说明符可能会改变最终的类大小...
      • Matthieu 的权利 - 11.1-2 和 9.2-12 - 隐约记得对此的讨论允许每次检查成员的偏移量也指示保护级别(即 if (some_class_member.offset >= first_private_offset) / * 它是私有的 */)。它也可能是为了促进使用一些真实的或假定的运行时安全机制(例如,使用硬件允许细粒度的内存访问控制,锁定/解锁私有作为私有成员函数调用/返回)。无论如何,对齐规则意味着顺序会影响填充的效率。
      【解决方案7】:

      我不知道这是什么语言,但是当您继承原始对象时仍然存在。这就是为什么你仍然可以调用 base.method()

      【讨论】:

        猜你喜欢
        • 2011-01-02
        • 1970-01-01
        • 1970-01-01
        • 2015-12-11
        • 2017-08-07
        • 2017-04-14
        • 1970-01-01
        • 2018-01-05
        • 1970-01-01
        相关资源
        最近更新 更多