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

Applying "using" keyword on C++ pure virtual function

本文关键字:using 关键字 C++ 应用 函数      更新时间:2023-10-16

类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 ()

基于我第一次尝试找到答案,@Oliv的评论和答案,让我尝试总结Cusing A::memberFct声明的所有可能场景。

  • 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点不指向纯虚拟成员函数实现。

这是因为使用声明不会引入新成员或新定义。相反,它引入了一组声明,这些声明可以通过限定名称查找[namespace.udcl]/1:找到

using声明中的每个using声明符,都将一组声明引入using声明所在的声明区域。通过对using声明符中的名称执行限定名称查找([basic.lookup.qual],[class.member.lookup]),可以找到using声明器引入的声明集,不包括如下所述隐藏的函数。

它只对通过限定名称查找找到的实体有影响。因此,它对最终覆盖器[class.virtual]/2:的定义没有影响

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

与以下含义不同:最后一个重写器是由表达式D::vf指定的实体,其中D是最派生的类,S是其基类子对象

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

如果一个类包含或继承了至少一个纯虚拟函数,而最终的重写器是纯虚拟的,那么它就是抽象的。


注1:

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

如果所选函数是非虚拟的,或者如果类成员访问表达式中的id表达式是限定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;               
}

现场演示