[英]Access of member functions in C++ inheritance
我对以下关于继承的小程序感到困惑:
#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
在B
D
也是公共的(我知道没有继承,默认情况下成员访问是私有的) f
是B
的虚函数,所以如果我们调用bf()
,我们实际调用D::f()
,但正如图中所提到的,为什么D::f()
能够被调用,即使它是私有的? 任何人都可以详细解释简单的继承问题吗?
这必须做到这一点,虚拟调度是一个运行时概念。 B
类并不关心哪个类扩展它,并且它不关心它是私有的还是公共的,因为它无法知道。
我无法弄清楚为什么D :: f()是私有的,D是公共继承自B,所以B中的公共函数f在D中也是公共的(我知道没有继承,默认情况下成员访问是私有的)
D::f()
是私有的,因为你把它变成了私有的。 此规则不受继承或虚拟分派的影响。
f是B中的虚函数,所以如果我们调用bf(),我们实际调用D :: f(),但正如图中所提到的,为什么D :: f()能够被调用,即使它是私有的?
因为实际上,在调用bf()
,编译器不知道实际调用哪个函数。 它将简单地调用函数f()
,并且由于B::f
是虚拟的,因此将在运行时选择被调用的函数。 运行时程序没有关于哪个功能是私有还是受保护的信息。 它只知道功能。
如果在运行时选择了该函数,则编译器无法在编译时知道将调用哪个函数,并且无法知道访问说明符。 实际上,编译器甚至不会尝试检查被调用的函数是否是私有的。 访问说明符可能在编译器还没有看到的某些代码中。
正如您所经历的那样,您无法直接调用D::f
。 这正是私人所做的:禁止直接访问会员。 但是,您可以通过指针或引用间接访问它。 您使用的虚拟调度将在内部执行。
访问说明符仅适用于函数名称 ,它们对通过其他方式调用函数的方式或时间没有一些限制。 如果通过除名称之外的某些方式(例如,函数指针)使私有函数可用,则可以在类外部调用私函数。
对于使用class
关键字声明的class
,默认的访问说明符是private
。 您的代码与以下内容相同:
// ...
class D: public B
{
private:
int f() { return 2; }
};
如你所见, f
在D
是私有的。 对于具有相同名称的B
的任何函数,访问说明符是什么没有区别。 请记住, B::f()
和D::f()
是两个不同的函数。
virtual
关键字的作用是如果在引用D
对象的B
引用上调用没有范围限定符的f()
,那么即使它解析为B::f()
,实际上D::f()
也是相反调用。
这个过程仍然使用B::f()
的访问说明符:在编译时检查访问; 但是关于调用哪个函数可能是运行时间问题。
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 }
- 结束例子 ]
无法解释清楚。
给出的答案说明了正在做什么,但为什么你会想要这样做,基类调用private
虚函数?
好吧,有一种称为模板方法模式的设计模式,它使用这种技术,它具有一个在派生类中调用私有虚函数的基类。
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
函数调用这些函数。
这实际上与虚拟调度关系较少,而与访问说明符的含义有关。
功能本身不是private
; 它的名字是。
因此,函数不能在类的范围之外命名,例如从main
。 但是,您仍然可以通过public
名称(即被覆盖的基本虚函数)或尽可能使用private
限定符(例如该类的成员函数)访问函数名称的作用域来执行此操作。
这就是它的工作原理。
为什么D :: f()能够被调用,即使它是私有的?
要了解虚函数机制,最好知道它是如何实现的。 运行时的函数实际上只不过是函数体的可执行代码所在的内存中的地址。 要调用该函数,我们需要知道它的地址(指针)。 内存中具有虚函数表示的C ++对象包含所谓的vtable - 一个指向虚函数的指针数组。
关键点是在派生类中,vtable重复(并可能扩展)基类的vtable,但是如果虚函数被覆盖,则其指针将被替换为派生对象的vtable。
当通过基类指针完成虚函数调用时,虚函数的地址计算为vtable数组中的偏移量。 没有进行其他检查,只需要执行功能地址。 如果它是基类对象,它将是基类函数的地址。 如果它是派生类对象,它将是派生类函数的地址,无论它是否被声明为私有都无关紧要。
这是怎么回事。
struct
成员默认为public,而class
成员默认为private。 所以B中的f()
是公共的,当它被导出到D时,因为你没有明确地声明它是公共的,所以根据推导规则,它变成了私有的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.