在C ++继承中访问成员函数

Access of member functions in C++ inheritance

我只是对下面关于继承的小程序感到困惑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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继承来的公有的,所以B中的公有功能f是从B继承来的。在D中也是公共的(我知道没有继承,成员访问默认是私有的)
  • fB中的一个虚函数,所以如果我们称它为b.f(),实际上我们称它为D::f(),但正如前面所述,为什么即使它是私有的,也能调用D::f()
  • 有人能详细解释这个简单的继承问题吗?


    这与虚拟调度是一个运行时概念有关。类B不关心哪个类扩展它,也不关心它是私有的还是公共的,因为它不知道。

    I can't figure out why D::f() is private, D is public inherited from B, so the public function f in B
    is also public in D(I know without inheritance, member access is private by default)

    D::f()是私有的,因为你把它变成了私有的。此规则不受继承或虚拟调度的影响。

    f is a virtual function in B, so if we call b.f(), we actually call D::f(), but just as the illustration mentioned, why D::f() is able to be invoked even though it's private?

    因为实际上,当调用b.f()时,编译器不知道实际调用哪个函数。它只调用函数f(),由于B::f是虚拟的,所以在运行时将选择被调用的函数。运行时程序没有关于哪个函数是私有的或受保护的信息。它只知道功能。

    如果函数是在运行时选择的,则编译器在编译时无法知道将调用什么函数,也无法知道访问说明符。实际上,编译器甚至不会尝试检查调用的函数是否是私有的。访问说明符可能在编译器尚未看到的某些代码中。

    正如你所经历的,你不能直接打电话给D::f。这正是private将要做的:禁止直接访问成员。但是,您可以通过指针或引用间接访问它。您使用的虚拟调度将在内部完成。


    访问说明符只应用于函数名,它们不限制如何或何时可以通过其他方法调用函数。如果私有函数的名称(例如,函数指针)以外的其他方式可用,则可以在类外部调用它。

    对于使用class关键字声明的类,默认访问说明符为private。您的代码与以下代码相同:

    1
    2
    3
    4
    5
    6
    // ...
    class D: public B
    {
    private:
      int f() { return 2; }
    };

    如你所见,fD中是私有的。访问说明符对于同一名称的B中的任何函数都没有区别。你要清楚,B::f()D::f()是两种不同的功能。

    virtual关键字的作用是,如果在引用D对象的B引用上调用没有作用域限定符的f(),那么即使它解析为B::f(),实际上也会调用D::f()

    这个过程仍然使用B::f()的访问说明符:在编译时检查访问;但是调用哪个函数可能与运行时有关。


    C++标准有一个确切的例子:

    11.5 Access to virtual functions [class.access.virt]

    1 The access rules (Clause 11) for a virtual function are determined by its
    declaration and are not affected by the rules for a function that later
    overrides it. [Example:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class B {
    public:
      virtual int f();
    };

    class D : public B {
    private:
      int f();
    };

    void f() {
      D d;
      B* pb = &amp;d;
      D* pd = &amp;d;
      pb->f();     // OK: B::f() is public,
                   // D::f() is invoked
      pd->f();     // error: D::f() is private
    }

    -- end example]

    解释不清楚。


    给出的答案说明了正在做什么,但是为什么您会想要这样做呢,在基类调用private虚拟函数的地方?

    好吧,有一个名为模板方法模式的设计模式,它使用了这样一种技术,即拥有一个在派生类中调用私有虚函数的基类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    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();
    }

    这允许我们不向public接口公开类D的自定义步骤,但同时允许B使用public函数调用这些函数。


    这实际上与虚拟调度的关系不大,而与访问说明符的含义有关。

    函数本身不是private;它的名称是。

    因此,函数的命名不能超出类的范围,例如从main中。但是,您仍然可以通过一个名为public(即被重写的基的虚拟函数)或从一个范围(在该范围内,尽管private限定符(如该类的成员函数)可以访问函数名)来执行此操作。

    这就是它的工作原理。


    why D::f() is able to be invoked even though it's private?

    要理解虚拟功能机制,最好了解它通常是如何实现的。运行时的函数实际上不超过函数体的可执行代码所在的内存中的地址。要调用函数,我们需要知道它的地址(指针)。在内存中具有虚函数表示的C++对象包含所谓的VTABLE——指向虚拟函数的指针数组。

    vtable typical implementation

    关键是在派生类vtable中重复(并且可以扩展)基类的vtable,但是如果覆盖了虚函数,则它的指针将在派生对象的vtable中替换。

    当虚拟函数调用通过基类指针完成时,虚拟函数的地址计算为vtable数组中的偏移量。不进行其他检查,只取函数地址。如果它是一个基类对象,它将是基类函数的地址。如果它是派生类对象,它将是派生类函数的地址,不管它是否声明为私有。

    这就是它的工作原理。


    EDOCX1的成员(25)默认为public,EDOCX1的成员(11)默认为private。所以B中的f()是公共的,当它派生为d时,因为你没有明确声明它是公共的,所以根据派生规则,它变成了私有的。