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

Gtr*_*rex 15 c++ overriding virtual-functions using-declaration

B类覆盖了A类的纯虚函数"print()".C类继承了B类,并且具有"使用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;               
}
Run Code Online (Sandbox Code Playgroud)

输出:

    Class B print ()
    Class B print ()
Run Code Online (Sandbox Code Playgroud)

lub*_*bgr 9

基于我第一次尝试找到答案,@ Oliv的评论和回答,让我试着总结一下using A::memberFct内部声明的所有可能场景C.

  • 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() {}
};
Run Code Online (Sandbox Code Playgroud)

通过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
Run Code Online (Sandbox Code Playgroud)

请注意,在类中使用声明的影响有限,即它们更改名称查找,但不更改虚拟调度(第一种情况).成员函数是否为纯虚函数不会更改此行为.当继承层次结构中的函数隐藏基类函数时(第二种情况),将调整查找,使得使用声明的那个具有优先权.当类本身的函数隐藏基类函数时(第三种情况),类本身的实现具有优先权,请参阅 cppreference:

如果派生类已具有具有相同名称,参数列表和限定条件的成员,则派生类成员将隐藏或覆盖(不冲突)从基类引入的成员.

在原始片段中,C因此不是抽象类,因为只有所讨论的成员函数的查找机制受使用声明的影响,并且vtable点不指向纯虚拟成员函数实现.


Oli*_*liv 6

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

using声明中的每个using-declarator都会在声明区域中引入一组声明,其中出现using声明.using-declarator引入的声明集是通过对using-declarator中的名称执行限定名称查找([basic.lookup.qual],[class.member.lookup])来找到的,不包括所描述的隐藏的函数下面.

所以使用声明不是声明.它只对通过限定名称查找找到的实体有影响.因此,它对最终覆盖 的定义没有影响[class.virtual]/2:

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

其含义不同于:最终覆盖是由表达式D :: vf指定的实体,其中D是派生类最多的类,其中S是基类子对象.

因此,它不会影响一个类是否是一个抽象类[class.abstract]/4:

如果一个类包含或继承至少一个最终覆盖为纯虚拟的纯虚函数,则该类是抽象的.


注1:

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

如果所选函数是非虚拟的,或者类成员访问表达式中的id-expression是qualified-id,则调用该函数.否则,调用它在对象表达式的动态类型中的最终覆盖; 这种调用称为虚函数调用.

只是:

  • 非虚函数=>通过限定名称查找找到的函数
  • virtual function =>调用最终的覆盖

所以,如果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;               
  }
Run Code Online (Sandbox Code Playgroud)

笔记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;               
  }
Run Code Online (Sandbox Code Playgroud)

现场演示