基本example:
class Base
{
public:
double mValue = 10.0;
};
class Derived: public Base
{
public:
double mAdditionalValue = 20.0;
};
int main()
{
Derived derived;
Base *rBase = &derived;
std::cout << "value " << rBase->mValue << '\n';
std::cout << "additionalValue " << rBase->mAdditionalValue << '\n';
}
显然,我无法通过mAdditionalValue
访问rBase
,因为引用类型(Base
)不是Derived
,并且不包含成员名为mAdditionalValue
。太好了!智能机制!但实际上这是如何在C ++中实现的?
我的意思是:rBase
无论如何都指向Derived
类型的对象,并且在内存中存储了mAdditionalValue
(我已经创建了它)。所以它指向整个对象。它可以&#34;物理&#34;访问该数据。
谁&#34;阻止/限制&#34;指针范围可访问性?它在指针的逻辑内(所以是软件实现)还是编译器在编译阶段,捕获它并阻止操作?那会怎么做?检查内存地址并限制它们?
答案 0 :(得分:5)
我要告诉你一个lie we tell children。就像当我们告诉他们为什么天空是蓝色的,或者世界是圆的时候。
所以编译器知道给定指向Base
的指针,mValue
位于Base
地址的固定偏移处(在上面的例子中,我打赌0)
因此它将rBase->mValue
转换为“从rBase指向的位置偏移0获取双精度”。它通过查看静态类型*rBase
(在编译时),查找mValue
在该类型中的的偏移量,并将代码重写为no来实现此目的更长时间了解rBase
的类型,只是指针和偏移量。
一切都很好。
当你要求rBase->mAdditionalValue
时,它会在编译时尝试在静态类型mAdditionalValue
中查找*rBase
的偏移量,但它不在那里。所以它会产生错误。
您可以想象一种语言会更进一步说“好吧,mAdditionalValue
是在派生类型中定义的!只需使用该偏移!”。
class Derived2: public Base
{
public:
double bob=0;
double mAdditionalValue = 20.0;
};
哦,哦,哦现在有两种派生类型,它们不同意mAdditionalValue
相对于指向对象的Base
指针的偏移量。所以现在这个非C ++编译器必须确定*rBase
的运行时类型来计算偏移量,这反过来需要开销。
C ++中有一种机制可以为方法(虚函数)而不是数据执行此操作,但即使在那里,您也必须在Base中使该方法成为虚拟(否则使高效表变得精神错乱)。从理论上讲,C ++可以拥有虚拟数据成员。
因此,一般来说,没有一种零成本的方法来解决你似乎想要的东西。
对于不同的谎言,你要求的东西违反了封装。您无法从mAdditionalValue
请求Base*
。即使很容易,C ++应该说不。如果我们盲目跟随指针的错误,那么运行时行为是不好的。它应该是明确的或不可能的。
真正的答案是,因为标准是这样说的。您必须将Base
的方法或数据字段的名称放在Base->
的右侧。其他任何事情都是形成不良的计划。
现在您可以编译代码了。
std::cout << "additionalValue " << static_cast<Derived*>(rBase)->mAdditionalValue << '\n';
这里我们告诉它我们知道它是派生的,如果我们错了,一切都可以用0运行时成本(如果我们是正确的话)和未定义的行为。
这是一种可能的内存布局:
....
0x1110FFFF
0x11110000 DERIVED BASE mValue
0x11110001 DERIVED BASE mValue
0x11110002 DERIVED BASE mValue
0x11110003 DERIVED BASE mValue
0x11110004 DERIVED BASE mValue
0x11110005 DERIVED BASE mValue
0x11110006 DERIVED BASE mValue
0x11110007 DERIVED BASE mValue
0x11110008 DERIVED mAnotherValue
0x11110009 DERIVED mAnotherValue
0x1111000A DERIVED mAnotherValue
0x1111000B DERIVED mAnotherValue
0x1111000C DERIVED mAnotherValue
0x1111000D DERIVED mAnotherValue
0x1111000E DERIVED mAnotherValue
0x1111000F DERIVED mAnotherValue
0x11110010
...
0x11110000
处的指针可能指向DERIVED
,BASE
或mValue
,甚至只指0x11110000
处的字节 - 他们有更多指针比一个感觉相同的地址。但是,给定的指针只指向其中一个指针,由指针的类型决定。
值为Base*
的{{1}}指针指向0x11110000
的{{1}}子对象。
答案 1 :(得分:0)
使用我认为您从评论中无法理解的内容完成@Yakk的答案:
代表基础并在内存中派生:
[碱]
[-------衍生]
你的指针将有&#39; [&#39;你的对象在内存中的开头。 如果你看一下派生,它的方式比基数大:它是因为它包含基数,实际上是派生的
[碱衍生]
现在当您执行以下操作时会发生什么?
Base *rBase = &derived;
这里你的指针指向一个基础
[碱]
^
即使在记忆中它实际上也是
[碱衍生]
^
总而言之,您的指针具有基址,即使此基数包含在派生内。由于你的指针具有基数的地址,它无法访问派生数据,因为在编译时它不知道这个。
在运行时,您可以将指针转换为指向派生的指针,您将没有问题。