我想知道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 ++如何确保这样的布局。
答案 0 :(得分:0)
内存中类的布局包括其成员和基类子对象(§10/ 2)。成员也是子对象。指向基础子对象的指针是指向对象的指针(但不是最派生的对象)。
将MDerived *
转换为Base2 *
时,编译器会查找Base2
对象中MDerived
对象的偏移量并使用它生成{{1}对于继承的方法。
答案 1 :(得分:0)
我想你问为什么,当你致电b1->function()
时,Base1::function1()
会发火吗?
如果是这样,那么原因是因为b1
是Base1
指针,而不是MDerived
指针。它指向的对象实际上可能“成为”MDerived
对象,但function(Base1*)
无法知道这一点,所以它只调用它知道的事物 - Base1::function1()
。< / p>
现在,如果您已将基类功能标记为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()
,因为这是可用的派生类型。
答案 2 :(得分:0)
您的代码中很少有东西
;
<iostream>
(好吧可能是我太迂腐)回答你的问题 - 编译器知道它是基类,因为函数所采用的参数类型是Base1。编译器转换传递数据的类型(假设您传递了派生对象,进行了对象切片),然后在其上调用function1()(这是与基本指针计算的简单偏移)。
答案 3 :(得分:0)
当需要将MDerived*
转换为Base1*
时,编译器会将指针调整为指向此基类成员所在的正确内存地址。这意味着转换为MDerived*
的{{1}}可能指向与原始Base1*
不同的内存地址(取决于派生类的内存布局)。
编译器可以这样做,因为它知道所有类的内存布局,并且当发生强制转换时,它可以添加调整指针地址的代码。
例如,这可能会打印不同的地址:
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;
}
或Base1
或Base2
则无关紧要。
类的非虚方法不与每个对象一起存储,它们只存储一次。然后编译器在编译时静态地根据所使用的变量的类型在调用成员函数时使用这些全局地址。