仅仅为了代码重用而不必要地使用虚拟函数
Unnecessary usage of virtual functions just for code reuse
我使用继承只是为了代码重用,我从不将类强制转换为基类。这是一个性能繁重的程序,所以我想避免使用virtual
函数,但我不知道如何使用。考虑以下代码
class A{
public:
void print(){
std::cout << "Result of f() is: " << f() << std::endl;
}
virtual std::string f(){
return "A";
}
};
class B : public A{
public:
virtual std::string f(){
return "B";
}
};
对于函数f()
不使用virtual
函数,而在类B
中不重新实现函数print()
,这可能吗?我不在乎A
是B
的基类,我只是不想再写f()
。也许继承不是办法,也许模板可以用聪明的方式使用,但我不知道。
CRTP模式通常用于避免动态调度,因为它可以静态地确定为特定方法调用什么实现方法。
在您的示例中,A
和B
都将从提供print()
方法的单个基类继承。基类,我们称之为Print
,是一个模板,其模板参数是一个提供f()
的类。使这种模式获得"奇怪"绰号的转折点是,子类必须继承在子类上模板化的基类。这允许子类访问基类的print
方法,但获得基类的一个版本,并通过扩展获得print
的版本,该版本调用它们自己的f
。
下面是一个工作代码示例:
#include <iostream>
template<typename F>
class Print {
public:
void print() {
F& final = static_cast<F&>(*this);
std::cout << "Result of f() is: " << final.f() << std::endl;
}
};
class A: public Print<A> {
public:
std::string f(){
return "A";
}
};
class B: public Print<B> {
public:
std::string f(){
return "B";
}
};
int main() {
A a;
B b;
a.print();
b.print();
}
尽管print
实现在A
和B
之间被重用,但这里没有虚拟方法,也没有虚拟(运行时)调度或运行时检查。出现的一个强制转换是static_cast<>
,其安全性已由编译器适当验证。
这是可能的,因为每次使用Print<F>
,编译器都确切地知道F
是什么。因此,已知Print<A>::print
调用A::f
,而Print<B>::print
调用B::f
,所有这些在编译时都是已知的。这使编译器能够像任何其他非虚拟方法调用一样内联和优化此类调用。
不利的一面是也没有继承。请注意,B
不是从A
派生的——如果是,则模式将不起作用,A::print
和B::print
都将打印A
,因为Print<A>
就是这样输出的。更根本的是,不能在需要A*
的地方传递B*
——这是未定义的行为。事实上,A
和B
不共享任何共同的超类,Parent<A>
和Parent<B>
类是完全分离的。失去运行时调度(有缺点也有好处),转而启用静态调度,是静态多态性的一个基本折衷。
如果出于任何原因,您不想要动态绑定的"开销",可以省略virtual
-关键字,这会使编译器使用静态绑定并禁用多态性。也就是说,编译器将在编译时仅根据变量的类型绑定实现,而不是在运行时。
老实说,我从来没有在不启用多态性的情况下为子类中的方法定义特定的实现。这样做通常表明没有特定的行为,即根本不应该在子类中重写该方法。
此外,将字符常量封装到字符串对象中的代码的性能成本已经远远高于动态绑定/vtable
的"开销"。真的,重新思考两次,然后在进行此类"优化"之前衡量性能增长。
不管怎样,如果省略virtual
,请查看代码的行为。注意,代码中的ptr->f()
绑定到A::f
,因为变量的类型是A*
,尽管它指向类型为B
:的对象
class A{
public:
void print(){
std::cout << "Result of f() is: " << f() << std::endl;
}
std::string f(){
return "A";
}
};
class B : public A{
public:
std::string f(){
return "B";
}
};
int main()
{
A a; cout << a.f(); // -> yields "A"
B b; cout << b.f(); // -> yields "B"
A* ptr = &b; cout << ptr->f(); // -> yields "A"; (virtual f, in contrast) would be "B"
return 0;
}
您可以使用模板来选择A和B的动态或非动态版本。这是一个相当棘手/丑陋的选项,但值得考虑。
#include <string>
template <bool Virt = false>
class A{
public:
std::string f(){
return "A";
}
};
template <>
class A<true> : A<false>{
public:
virtual std::string f(){
return A<false>::f();
}
};
template <bool Virt = false>
class B : public A<Virt>{
public:
std::string f(){
return "B";
}
};
std::string f1() { return B<>().f(); }
std::string f2(A<true> &a) { return a.f(); }
std::string f3() { B<true> b; return f2(b); }
#include <iostream>
int main(){
std::cout << f1() << 'n';
std::cout << f3() << 'n';
return(0);
}
值得注意的一点是,除了C++(预模板)早期做出的有争议的决定外,虚拟关键字在重写时应该是可选的,这是不可能的。
- C++无法定义虚拟函数 OUTER 类和头文件
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 尝试将unique_ptrs推送到向量时使用纯虚拟函数错误
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 类型擦除的std::function与虚拟函数调用的开销
- 重写虚拟函数和继承
- 用纯虚拟函数兜圈子
- 为什么使用存储在虚拟方法表中的地址调用虚拟函数的函数会返回垃圾?
- 禁止子函数调用父级的抽象(或虚拟)函数
- 无法在子类中使用虚拟函数C++
- 无法在派生对象上运行虚拟函数
- 我可以调用从 main() 覆盖的虚拟函数吗?
- 在 C++ 中将函数获取和设置为虚拟函数
- 使用在堆栈上创建的对象调用虚拟函数
- 为什么在这种情况下不调用我的虚拟函数实现?
- 在C++中使虚拟函数私有化
- 模板继承类中的虚拟函数
- 为什么构造函数的虚拟函数调用有时有效,但其他调用却无效
- doxygenc++虚拟函数和实现
- 如何从派生类函数中调用虚拟函数