在纯虚函数上应用 "using" 关键字C++
Applying "using" keyword on C++ pure virtual function
类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的评论和答案,让我尝试总结C
中using 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;
}
现场演示
- 是否建议在函数中使用using关键字
- 如何使用 "using" 关键字定义函数原型/签名
- C++使用 using 关键字键入别名
- C++带有 using 关键字和参数包的模板函数
- 如何将"using"关键字用于可变参数模板
- 如何在C++中使用"using"关键字
- 使用 "using" 关键字继承基类的复制和移动构造函数
- 在纯虚函数上应用 "using" 关键字C++
- C++- "using"关键字或使用带有范围解析运算符的命名空间名称
- 使用 "using" 关键字作为伪预处理器
- 如何在方法中使用 make "using" 关键字以接受它作为类级别
- "using"关键字在 c++ 中究竟有什么作用?
- C++中"using"关键字背后的逻辑是什么?
- C++11`using`关键字:专门化模板参数的模板别名
- C++避免使用 using 关键字在派生类中进行动态调度
- 如何使用模板函数C++ 11 的 using 关键字来删除作用域中的命名空间
- 了解'using'关键字:C++
- 为什么VC++2013拒绝编译嵌套类型,当用作模板函数返回类型时,用using关键字使其可见
- 不赞成使用访问声明,而是使用-declarations;建议:添加“using”关键字
- 调用基类构造函数的"Using"关键字