类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 ()
答案 0 :(得分: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() {}
};
通过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;
}