【问题标题】:how C++ ensures the concept of base class and derived classC++如何保证基类和派生类的概念
【发布时间】:2010-09-14 20:38:15
【问题描述】:

我想知道c++如何确保这些类在内存中的概念布局以支持继承。

例如:

class Base1
{

    public:
        void function1(){cout<<"Base1"};

};

class Base2
{

    public:
        void function2(){cout<<"Base2"};

};

class MDerived: Base1,Base2
{

    public:
        void function1(){cout<<"MDerived"};

};

void function(Base1 *b1)
{

    b1->function1();
}

因此,当我将派生类型的对象传递给函数时,该函数应该偏移到 base1 类函数中并调用它。 C++如何保证这样的布局。

【问题讨论】:

  • 您的 MDerived::function1 方法正在打印“Base1”,我认为这不是您想要的。我已经编辑了你的帖子。
  • 可以请一些编译器应该生成的内存布局。以及派生对象如何转换为基类。因为派生内存中有两个基类,编译器如何知道要选择哪个偏移量。
  • 该标准没有定义内存布局,这由实现者决定。严格来说,这个问题中的每个答案都是在讨论一些编译器用来实现虚函数的平台特定方式。

标签: c++


【解决方案1】:

当需要将MDerived* 转换为Base1* 时,编译器会调整指针以指向正确的内存地址,即该基类的成员所在的位置。这意味着转换为Base1*MDerived* 可能指向与原始MDerived* 不同的内存地址(取决于派生类的内存布局)。

编译器可以这样做,因为它知道所有类的内存布局,并且当发生强制转换时,它可以添加调整指针地址的代码。

例如,这可能会打印不同的地址:

int main() {
   MDerived *d = new MDerived;
   std::cout << "derived: " << d << std::endl;
   std::cout << "base1: " << (base1*)d << std::endl;
   std::cout << "base2: " << (base2*)d << std::endl;
}

在您的示例中,可能不需要进行此类调整,因为类不包含任何成员变量,这些成员变量会使用表示基类的子对象中的任何内存。如果您有一个指向“无”(无成员变量)的指针,那么该无所谓 Base1Base2MDerived 并不重要。

类的非虚方法不与每个对象一起存储,它们只存储一次。然后编译器在编译时静态地在调用成员函数时根据使用的变量的类型使用这些全局地址。

【讨论】:

    【解决方案2】:

    内存中类的布局包括其成员和基类子对象(第 10/2 节)。成员也是子对象。指向基础子对象的指针是指向对象(但不是最派生对象)的指针。

    当您将MDerived * 转换为Base2 * 时,编译器会在MDerived 对象内查找Base2 对象的偏移量,并使用它为继承的方法生成this

    【讨论】:

    • 编译器如何知道偏移量。它存储在某个地方吗?
    • 它是结构定义的一部分。它被编译器记住,但不直接存储在编译的程序中。您可以说它“存储”在从派生指针生成基指针的 ADD 指令中。
    【解决方案3】:

    认为你问为什么,当你打电话给b1-&gt;function() 时,Base1::function1() 会起火?

    如果是这样,那么原因是因为b1Base1 指针,而不是MDerived 指针。它指向的对象实际上可能“是”一个MDerived 对象,但function(Base1*) 无法知道这一点,所以它调用它唯一知道的东西——Base1::function1()

    现在,如果您将基类函数标记为virtual,情况会发生变化:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Base1 
    {
    public: virtual void function1() { cout<<"Base1"; }
    };
    
    class Base2 
    {
    public: void function2(){cout<<"Base2";}
    };
    
    class MDerived: public Base1, public Base2 
    {
    public: void function1(){cout<<"MDerived";}
    };
    
    void function(Base1 *b1) 
    {
        b1->function1();
    }
    
    int main()
    {
        MDerived d;
        function(&d);
    }
    

    程序的输出是:

    “MDerived”

    void function(Base1 *b1) 仍然不知道被指向的对象实际上是一个MDerived,但现在它不必知道。当您通过基类指针调用virtual 函数时,您会得到polymorphic 行为。在这种情况下,这意味着 MDerived::function1() 被调用,因为这是可用的派生最多的类型。

    【讨论】:

      【解决方案4】:

      你的代码中的一些东西

      1. 多重继承应该是公开的,否则编译器会报错说无法访问基类
      2. cout 语句末尾没有;
      3. 不包括&lt;iostream&gt;(好吧可能是我太迂腐了)

      回答您的问题 - 编译器知道它是基类,因为函数采用的参数类型是 Base1。编译器转换所传递数据的类型(假设你传递了一个派生对象,进行对象切片),然后在其上调用 function1()(这是基指针计算的简单偏移量)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-11-16
        • 2015-07-23
        • 1970-01-01
        • 2012-01-23
        • 1970-01-01
        • 1970-01-01
        • 2016-10-17
        相关资源
        最近更新 更多