我对以下关于继承的小程序感到困惑:
#include<iostream>
using namespace std;
struct B {
virtual int f() { return 1; }
}; // f is public in B
class D : public B {
int f() { return 2; }
}; // f is private in D
int main()
{
D d;
B& b = d;
cout<<b.f()<<endl; // OK: B::f() is public, D::f() is invoked even though it's private
cout<<d.f()<<endl; // error: D::f() is private
}
D::f()
是私有的,D
是来自B
的公共继承,所以公共函数f
in B
D
中也是公开的(我知道没有继承,默认情况下成员访问是私有的)f
是B
中的虚拟函数,因此,如果我们调用b.f()
,我们实际上会调用D::f()
,但正如图中所述,为什么{{1}即使它是私有的,也可以被调用吗?任何人都可以详细解释简单的继承问题吗?
答案 0 :(得分:20)
这必须做到这一点,虚拟调度是一个运行时概念。类B
并不关心哪个类扩展它,并且它不关心它是私有的还是公共的,因为它无法知道。
我无法弄清楚为什么D :: f()是私有的,D是公共继承自B的,所以B中的公共函数f 在D中也是公共的(我知道没有继承,默认情况下成员访问是私有的)
D::f()
是私密的,因为您将其设为私有。此规则不受继承或虚拟分派的影响。
f是B中的虚函数,所以如果我们调用bf(),我们实际上调用D :: f(),但正如图中所提到的,为什么D :: f()能够被调用,即使它是私人的?
因为实际上,在调用b.f()
时,编译器不知道实际调用哪个函数。它将简单地调用函数f()
,并且由于B::f
是虚拟的,因此将在运行时中选择被调用的函数。运行时程序没有关于哪个功能是私有还是受保护的信息。它只知道功能。
如果在运行时选择了该函数,则编译器无法在编译时知道将调用哪个函数,并且无法知道访问说明符。实际上,编译器甚至不会尝试检查被调用的函数是否是私有的。访问说明符可能在编译器尚未看到的某些代码中。
根据您的经验,您无法直接致电D::f
。这正是私人所做的:禁止直接访问会员。但是,您可以通过指针或引用间接访问它。您使用的虚拟调度将在内部执行。
答案 1 :(得分:11)
访问说明符仅适用于函数名称,它们对通过其他方式调用函数的方式或时间没有一些限制。如果通过除名称之外的某些方式(例如,函数指针)使私有函数可用,则可以在类外部调用私有函数。
对于使用class
关键字声明的类,默认访问说明符为private
。您的代码与:
// ...
class D: public B
{
private:
int f() { return 2; }
};
如您所见,f
在D
中是私有的。访问说明符与B
中具有相同名称的任何函数的含义没有区别。请注意,B::f()
和D::f()
是两个不同的功能。
virtual
关键字的效果是,如果在引用f()
对象的B
引用上调用没有范围限定符的D
,那么即使它解析为B::f()
,实际调用D::f()
。
此进程仍使用B::f()
的访问说明符:在编译时检查访问;但是关于调用哪个函数可能是运行时的问题。
答案 2 :(得分:6)
C ++标准有一个确切的例子:
11.5访问虚拟函数[class.access.virt]
1虚拟功能的访问规则(第11条)由其确定 声明,不受以后函数规则的影响 覆盖它。 [示例:
class B { public: virtual int f(); }; class D : public B { private: int f(); }; void f() { D d; B* pb = &d; D* pd = &d; pb->f(); // OK: B::f() is public, // D::f() is invoked pd->f(); // error: D::f() is private }
- 结束示例]
无法解释清楚。
答案 3 :(得分:5)
给出的答案说明了正在做什么,但为什么你会想要这样做,基类调用private
虚函数?
嗯,有一个名为template method pattern的设计模式,它使用这种技术,在派生类中调用私有虚函数。
struct B
{
virtual ~B() {};
int do_some_algorithm()
{
do_step_1();
do_step_2();
do_step_3();
}
private:
virtual void do_step_1() {}
virtual void do_step_2() {}
virtual void do_step_3() {}
};
class D : public B
{
void do_step_1()
{
// custom implementation
}
void do_step_2()
{
// custom implementation
}
void do_step_3()
{
// custom implementation
}
};
int main()
{
D dInstance;
B * pB = &dInstance;
pB->do_some_algorithm();
}
这样我们就不会将D
类的自定义步骤公开到public
界面,但同时允许B
使用public
来调用这些函数功能。
答案 4 :(得分:3)
这实际上与虚拟调度关系不大,而与访问说明符的含义有关。
该功能本身不是private
;它的名称是。
因此,该函数不能在类的范围之外命名,例如来自main
。但是,您仍然可以通过public
的名称(即被覆盖的基本虚函数)或尽管{{private
可以访问函数名称的范围来执行此操作。限定符(例如该类的成员函数)。
这就是它的运作方式。
答案 5 :(得分:1)
为什么D :: f()能够被调用,即使它是私有的?
要了解虚函数机制,最好知道它是如何实现的。运行时的函数实际上只不过是函数体的可执行代码所在的内存中的地址。要调用该函数,我们需要知道它的地址(指针)。内存中具有虚函数表示的C ++对象包含所谓的 vtable - 一个指向虚函数的指针数组。
关键是在派生类中,vtable重复(并可能扩展)基类的vtable,但是如果虚函数被覆盖,则它的指针在派生对象的vtable中被替换。
当通过基类指针完成虚函数调用时,虚函数的地址计算为vtable数组中的偏移量。没有进行其他检查,只需要执行功能地址。 如果它是基类对象,它将是基类函数的地址。如果它是派生类对象,它将是派生类函数的地址,无论它是否被声明为私有都无关紧要。
这是怎么回事。
答案 6 :(得分:0)
struct
的成员默认为公开,class
的成员默认为私有。
所以B中的f()
是公开的,当它被导出到D时,因为你没有明确地声明它是公共的,所以根据推导规则,它变成了私有的。