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

Posted

技术标签:

【中文标题】C++继承中成员函数的访问【英文标题】:Access of member functions in C++ inheritance 【发布时间】:2017-01-07 11:34:01 【问题描述】:

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

#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() 是私有的,DB 继承的,所以B 中的公共函数fD也是公开的(我知道没有继承,成员访问默认是私有的) fB中的一个虚函数,所以如果我们调用b.f(),我们实际上是调用D::f(),但是就像上面的插图,为什么D::f()是私有的,也能被调用?李>

谁能详细解释一下简单的继承问题?

【问题讨论】:

我不明白为什么 D::f() 是私有的 -- 因为它是一个private 函数。当然不是public。你有一个class,你没有访问说明符,函数是private 那么问题来了:为什么还能通过基类的函数调用呢?合理的推论... 拥有派生类private的成员函数不会关闭虚拟机制。谢天谢地。许多设计模式都使用这种技术(例如模板模式),派生类将虚函数实现为私有(或受保护)。 我知道默认访问说明符是私有的,但是由于 D 是从 B 继承的 public,B 中的 public 函数也应该是 公开在D中,我的误会在哪里? 作为比较,请注意在 Java 中不能降低继承方法的可见性(因此,如果方法在超类中是公共的,则不能在子类中设为私有)。 【参考方案1】:

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

我不明白为什么 D::f() 是私有的,D 是从 B 继承的公共函数,所以 B 中的公共函数 f 在 D 中也是公共的(我知道没有继承,成员访问默认是私有的)

D::f() 是私有的,因为您将其设为私有。此规则不受继承和虚拟调度的影响。

f是B中的虚函数,所以如果我们调用bf(),我们实际上是调用了D::f(),但是正如上面的插图,为什么D::f()即使被调用也能被调用是私人的吗?

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

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

根据您的经验,您不能直接致电D::f。这正是 private 会做的:禁止成员的直接访问。但是,您可以通过指针或引用间接访问它。您使用的虚拟调度将在内部执行此操作。

【讨论】:

【参考方案2】:

为什么 D::f() 是私有的也能被调用?

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

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

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

这就是它的工作原理。

【讨论】:

如何实现虚拟调度完全无关紧要!我想说的是,与其痴迷于内部结构,学习如何抽象地思考更有价值。 C++ 标准已经为我们准备好了抽象,所以我们只需要将我们的语言限制在它的范围内。 :)【参考方案3】:

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

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

因此,函数不能在类范围之外命名,例如来自main。但是,您仍然可以通过 public 的名称(即被覆盖的基类的虚函数)或尽管有 private 限定符但可以访问函数名称的范围(例如该类的成员函数)来执行此操作)。

这就是它的工作原理。

【讨论】:

【参考方案4】:

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
 

-- 结束示例]

无法解释清楚。

【讨论】:

“虚函数的访问规则...”有点牵强,同样的规则适用于虚函数和非虚函数。在这个例子中取出virtual,仍然是pb-&gt;f();正确而pd-&gt;f()不正确的情况 @M.M 对于 OP 的问题的第 1 部分来说,这有点红鲱鱼,但它在第 2 部分中是正确的,即为什么 pb-&gt;f() 调用私有派生版本。如果没有虚拟,这将不再有效。【参考方案5】:

struct 的成员默认为公开,class 的成员默认为私有。 所以 B 中的f() 是公共的,当它派生到 D 时,因为你没有明确声明它是公共的,所以根据派生规则,它变成了私有的。

【讨论】:

【参考方案6】:

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

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

// ...
class D: public B

private:
  int f()  return 2; 
;

如您所见,fD 中是私有的。访问说明符与B 中具有相同名称的任何函数无关。请记住,B::f()D::f() 是两个不同的函数。

virtual 关键字的效果是,如果在引用 D 对象的 B 引用上调用不带范围限定符的 f(),那么即使它解析为 B::f(),实际上也是 @而是调用 987654335@。

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

【讨论】:

我有一个问题,如果D中没有定义f(),那么D::f()B::f()的功能一样吗?在class D公开吗? @EricLuo 在那种情况下D::f()B::f() 确实是同一个函数,通过名称D::f() 访问函数是由继承规则决定的。 C++ 标准中的确切文本是,“除非在派生类中重新声明,否则基类的成员也被视为派生类的成员。” 终于明白了!还有一个请求,你介意告诉我确切的文字在哪里吗? @EricLuo see here 查找标准草案(例如 N4140、N3936)然后检查 [class.derived]/2 部分 或者 [class.access.virt] 看我的回答【参考方案7】:

给出的答案说明了正在做什么,但你为什么要这样做,基类调用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 函数调用这些函数。

【讨论】:

该示例没有说明 OP 问题的用例,因为 B 中的虚函数是私有的(以及它们在 D 中的实现)。从main 用户无法以任何方式呼叫他们,无论是通过虚拟呼叫还是通过直接呼叫。要说明的是为什么禁止直接调用D::f 是有用的,即使用户可以通过将D 对象引用转换为B 引用,然后虚拟调用f 方法来规避这一点。

以上是关于C++继承中成员函数的访问的主要内容,如果未能解决你的问题,请参考以下文章

C++继承

C++继承

C++继承

在c++中的继承,如何在子类中重载成员函数

C++ 继承同名成员处理方式

C++继承(上)