在C ++纯虚函数上应用“ using”关键字

时间:2019-01-04 12:21:46

标签: c++ override virtual-functions using-declaration

类B覆盖了类A的纯虚函数“ print()”。类C继承了类B并具有“ using A :: print”语句。 现在为什么C类不是抽象类?

class A {
    public :
        virtual void print() =0;
};

class B:public A {
    public:
        void print();
};

void B :: print() {

    cout << "\nClass B print ()";
}

class C : public B {

    public:
        using A::print;
};

void funca (A *a) {

    // a->print(1);                    
}

void funcb (B *b) {

    b->print();         
}

void funcc (C *c) {

    c->print();             
}

int main() {
    B b;
    C c;        
    funca(&c);              
    funcb(&c);              
    funcc(&c);              
    return 0;               
}

输出:

    Class B print ()
    Class B print ()

2 个答案:

答案 0 :(得分:9)

基于我第一次尝试找到答案,@ Oliv的评论和答案,让我尝试总结using A::memberFctC声明的所有可能情况。

  • A的成员函数是虚拟的,并被B
  • 覆盖
  • A的成员函数是非虚拟的,并被B隐藏
  • A的成员函数是非虚拟的,并且被C本身隐藏了

这些情况的一个小例子如下。

struct A {
   virtual void f() {}
   void g() {}
   void h() {}
};

struct B : A {
   void f() override {}
   void g() {}
};

struct C : B {
   using A::f; // Virtual function, vtable decides which one is called
   using A::g; // A::g was hidden by B::g, but now brought to foreground
   using A::h; // A::h is still hidden by C's own implementation
   void h() {}
};

通过C的界面调用这三个函数会导致不同的函数调用:

C{}.f(); // calls B::f through vtable
C{}.g(); // calls A::g because of using declarative
C{}.h(); // calls C::h, which has priority over A::h

请注意,在类内部使用声明的影响有限,即,它们可以更改名称查找,但不能更改虚拟调度(第一种情况)。成员函数是否为纯虚函数都不会更改此行为。当基类函数被继承层次结构中的函数隐藏时(第二种情况),将对查找进行调整,以使使用using声明的对象具有优先权。当基类函数被类本身的函数隐藏时(第三种情况),该类本身的实现具有优先权,请参见 cppreference

  

如果派生类已经具有具有相同名称,参数列表和限定的成员,则派生类成员将隐藏或重写(与之冲突)从基类引入的成员。

在您的原始代码段中,C因此不是抽象类,因为只有所讨论的成员函数的查找机制受到using声明的影响,而vtable点并不指向纯虚函数成员函数的实现。

答案 1 :(得分:6)

这是因为使用声明不是声明[namespace.udecl]/1,而是引入了一组声明,可以通过限定名称查找:

  

使用声明中的每个using-declarator,将一组声明引入使用声明中出现的声明区域。通过对using-declarator中的名称执行合格的名称查找([basic.lookup.qual],[class.member.lookup]),可以找到using-declarator引入的声明集,但不包括如上所述隐藏的函数。在下面。

因此using声明不是声明。它仅对通过合格名称查找找到的实体有影响。因此,它对最终替代程序 [class.virtual]/2的定义没有影响:

  

[...]类对象S的虚拟成员函数C :: vf是最终重写器,除非其S是基类子对象(如果有)的最派生类([intro.object])< strong>声明或继承另一个覆盖vf的成员函数。

其含义不同于:最终的替代者是由表达式D :: vf指定的实体,其中D是派生程度最高的类,其中S是基类对象。

因此,它不影响类是否为抽象类[class.abstract]/4

  

如果一个类包含或继承了至少一个纯虚拟函数,而其最终重写程序是纯虚拟的,则该类为抽象。


注1:

结果是using指令将对非虚函数和虚函数产生不同的行为[expr.call] / 3:

  

如果所选函数为非虚拟函数,或者类成员访问表达式中的id表达式为qualified-id,则会调用该函数。   否则,将调用对象表达式的动态类型中的最终重写器;这样的调用称为虚拟函数调用。

简单地:

  • 非虚拟功能=>通过限定名称查找找到的功能
  • 虚拟功能=>调用最终的替代程序

因此,如果print不是虚拟的:

class A {
  public :
  void print() {
    std::cout << "\n Class A::print()";
    }
  };

int main() {
  B b;
  C c;        
  b.print() // Class B print ()
  c.print() // Class A print ()
  //Equivalent to:
  c.C::print() // Class A::print()             
  return 0;               
  }

注意2:

正如在前面的标准段落中可能已经注意到的那样,可以执行对虚函数的限定调用以获得非虚行为。因此,使用虚函数的声明可能是可行的(可能是一种不好的做法):

class A {
  public :
  virtual void print() =0;
  };

//Warning arcane: A definition can be provided for pure virtual function
//which is only callable throw qualified name look up. Usualy an attempt
//to call a pure virtual function through qualified name look-up result
//in a link time error (that error message is welcome).
void A::print(){ 
  std::cout << "pure virtual A::print() called!!" << std::endl;
  }

int main() {
  B b;
  C c;        
  b.print() // Class B print ()
  c.print() // Class B print ()
  c.C::print() // pure virtual A::print() called!!
  //whitout the using declaration this last call would have print "Class B print()"              
  return 0;               
  }

Live demo