【问题标题】:Access of member functions in C++ inheritanceC++继承中成员函数的访问
【发布时间】: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
}
  1. 我不明白为什么D::f() 是私有的,DB 继承的,所以B 中的公共函数f
    D也是公开的(我知道没有继承,成员访问默认是私有的)
  2. fB中的一个虚函数,所以如果我们调用b.f(),我们实际上是调用D::f(),但是就像上面的插图,为什么D::f()是私有的,也能被调用?李>

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

【问题讨论】:

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

标签: c++ inheritance private


【解决方案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 方法来规避这一点。
        猜你喜欢
        • 2010-11-23
        • 1970-01-01
        • 2019-06-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多