如果它们可以是虚函数,那么有什么情况下会使用这样的函数的例子呢?
模板是关于编译器在编译时生成代码的。虚函数是关于运行时系统在运行时 figuring out 哪个函数需要被调用。
一旦运行时系统 figured out 需要调用一个模版化的虚函数,编译工作就完成了,编译器无法再生成适当的实例。因此,你不能有虚成员函数模板。
然而,有一些强大而有趣的技术结合了多态性和模板,特别是所谓的 type erasure。
void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }
,那么它“知道”在调用cb.f()
时调用了哪个函数,并且不知道vb.f()
调用的是哪个函数。后者必须在运行时由运行时系统来查找。无论您是否称其为“查找”,以及这是否更有效率,这些事实都没有改变。 - sbi来自《C++模板完全指南》:
成员函数模板不能被声明为虚函数。这是因为通常实现虚函数调用机制使用一个固定大小的表格,每个虚函数有一个条目。然而,成员函数模板的实例化数量在整个程序被翻译之前并不确定。因此,支持虚成员函数模板需要在C++编译器和链接器中支持一种全新的机制。相比之下,类模板的普通成员函数可以是虚函数,因为它们的数量在类被实例化时已经固定。
dlopen
动态打开这些共享库。此时的链接过程将会很麻烦,可能需要重新创建已经存在于内存中的对象的虚表! - CygnusX1目前C++不允许虚拟模板成员函数。最可能的原因是实现的复杂性。Rajendra提供了为什么现在不能完成它的好理由,但通过合理的标准更改,这是可能的。特别是要确定一个模板化函数的实例化数量并建立虚函数表,在考虑虚函数调用的位置时似乎很困难。标准人员现在只有很多其他事情要做,而且为编译器编写者来说,C++1x也是很多工作。
何时需要模板成员函数?我曾经遇到过这样一种情况,我试图重构具有纯虚基类的层次结构。它是实现不同策略的一种不良风格。我想将其中一个虚函数的参数更改为数字类型,而不是在所有子类中重载成员函数并覆盖每个重载,我尝试使用虚拟模板函数(结果发现它们不存在)。
让我们先从虚函数表的背景和工作原理开始 (来源):
[20.3] 虚函数和非虚函数调用的区别是什么?
非虚函数使用静态解析。也就是说,成员函数在编译时根据指向对象的指针(或引用)的类型静态选择。
相比之下,虚成员函数使用动态解析。也就是说,成员函数在运行时根据对象的类型动态选择,而不是指向该对象的指针/引用的类型。这被称为“动态绑定”。大多数编译器使用以下技术的变体: 如果对象具有一个或多个虚函数,则编译器在对象中放置一个名为“虚指针”或“v指针”的隐藏指针。 这个v指针指向一个全局表,称为“虚表”或“v表”。
编译器为每个至少有一个虚函数的类创建一个虚表。例如,如果圆形类具有用于draw()、move()和resize()的虚函数,则与圆形类关联的恰好有一个v表,即使有亿万个圆形对象,每个圆形对象的v指针也将指向圆形v表。 v表本身具有指向类中每个虚函数的指针。例如,圆形v表将具有三个指针:一个指向Circle::draw()、一个指向Circle::move()和一个指向Circle::resize()。
在调度虚函数期间,运行时系统跟随对象的v指针到类的v表,然后跟随v表中的适当插槽到方法代码。
上述技术的空间成本开销很小:每个对象多一个指针(但仅限于需要进行动态绑定的对象),每个方法多一个指针(但仅限于虚方法)。时间成本开销也相当小:与普通函数调用相比,虚函数调用需要两个额外的获取操作(一个获取v指针的值,第二个获取方法的地址)。由于编译器根据指针的类型解析非虚函数,所以所有这些运行时活动都不会发生在非虚函数中。
我现在正在尝试为一个立方体文件基类使用类似这样的东西,该基类具有模板化的优化加载函数,对于不同类型的立方体将有不同的实现方式(一些按像素存储,一些按图像存储等)。
一些代码:
virtual void LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
template<class T>
virtual void LoadCube(UtpBipCube<T> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
我最终将模板声明移动到类级别。这个解决方案会强制程序在读取数据之前就要了解它们的具体类型,这是不可接受的。
警告,虽然不太美观,但它允许我删除重复执行代码
1)在基类中
virtual void LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
2) 并且在子类中
void LoadCube(UtpBipCube<float> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }
void LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }
void LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }
template<class T>
void LoadAnyCube(UtpBipCube<T> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1);
template<typename T>
class Foo {
public:
template<typename P>
void f(const P& p) {
((T*)this)->f<P>(p);
}
};
class Bar : public Foo<Bar> {
public:
template<typename P>
void f(const P& p) {
std::cout << p << std::endl;
}
};
int main() {
Bar bar;
Bar *pbar = &bar;
pbar -> f(1);
Foo<Bar> *pfoo = &bar;
pfoo -> f(1);
};
如果你只想拥有一个通用接口并将实现延迟到子类中,那么这种方法的效果基本相同。
Foo
指针被限定为 Foo<Bar>
,它无法指向 Foo<Barf>
或者 Foo<XXX>
。 - Kai Petzke以下代码可以使用MinGW G++ 3.4.5在Windows 7上编译并正确运行:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class A{
public:
virtual void func1(const T& p)
{
cout<<"A:"<<p<<endl;
}
};
template <typename T>
class B
: public A<T>
{
public:
virtual void func1(const T& p)
{
cout<<"A<--B:"<<p<<endl;
}
};
int main(int argc, char** argv)
{
A<string> a;
B<int> b;
B<string> c;
A<string>* p = &a;
p->func1("A<string> a");
p = dynamic_cast<A<string>*>(&c);
p->func1("B<string> c");
B<int>* q = &b;
q->func1(3);
}
输出结果为:
A:A<string> a
A<--B:B<string> c
A<--B:3
之后我添加了一个新的类X:
class X
{
public:
template <typename T>
virtual void func2(const T& p)
{
cout<<"C:"<<p<<endl;
}
};
当我尝试在main()函数中使用X类时,就像这样:
X x;
x.func2<string>("X x");
使用g++编译时,报出以下错误:
vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'
显然有:
不行,模板成员函数不能是虚函数。
#include <memory>
#include <iostream>
#include <iomanip>
//---------------------------------------------
// Abstract class with virtual functions
class Geometry {
public:
virtual void getArea(float &area) = 0;
virtual void getArea(long double &area) = 0;
};
//---------------------------------------------
// Square
class Square : public Geometry {
public:
float size {1};
// virtual wrapper functions call template function for square
virtual void getArea(float &area) { getAreaT(area); }
virtual void getArea(long double &area) { getAreaT(area); }
private:
// Template function for squares
template <typename T>
void getAreaT(T &area) {
area = static_cast<T>(size * size);
}
};
//---------------------------------------------
// Circle
class Circle : public Geometry {
public:
float radius {1};
// virtual wrapper functions call template function for circle
virtual void getArea(float &area) { getAreaT(area); }
virtual void getArea(long double &area) { getAreaT(area); }
private:
// Template function for Circles
template <typename T>
void getAreaT(T &area) {
area = static_cast<T>(radius * radius * 3.1415926535897932385L);
}
};
//---------------------------------------------
// Main
int main()
{
// get area of square using template based function T=float
std::unique_ptr<Geometry> geometry = std::make_unique<Square>();
float areaSquare;
geometry->getArea(areaSquare);
// get area of circle using template based function T=long double
geometry = std::make_unique<Circle>();
long double areaCircle;
geometry->getArea(areaCircle);
std::cout << std::setprecision(20) << "Square area is " << areaSquare << ", Circle area is " << areaCircle << std::endl;
return 0;
}
输出:
正方形面积为1,圆形面积为3.1415926535897932385
在这里尝试一下
虽然这是一个被许多人回答过的旧问题,但我认为一个简洁的方法(与其他发布的方法并没有太大区别)是使用一个小巧的宏来帮助减少类声明的重复。
// abstract.h
// Simply define the types that each concrete class will use
#define IMPL_RENDER() \
void render(int a, char *b) override { render_internal<char>(a, b); } \
void render(int a, short *b) override { render_internal<short>(a, b); } \
// ...
class Renderable
{
public:
// Then, once for each on the abstract
virtual void render(int a, char *a) = 0;
virtual void render(int a, short *b) = 0;
// ...
};
现在,为了实现我们的子类:
class Box : public Renderable
{
public:
IMPL_RENDER() // Builds the functions we want
private:
template<typename T>
void render_internal(int a, T *b); // One spot for our logic
};
()
。它不是用来调用的,而是通过预编译器填充所需的函数。否则,你将不得不重新定义所有函数。(例如 Box::render(int, char *)
,Box::render(int, short *)
,等等) - mccatnm