一个C ++类的成员函数模板可以是虚拟的吗?

我听说C ++类成员函数模板不能是虚拟的。 这是真的?

如果它们可以是虚拟的,那么可以使用这种function的场景的例子是什么?

模板都是关于编译器在编译时生成代码的。 虚拟函数全部是关于运行时系统找出在运行时调用哪个函数的。

一旦运行时系统计算出来,就需要调用一个模板化的虚拟函数,编译完成,编译器不能再生成适当的实例。 所以你不能有虚拟成员函数模板。

然而,有几个强大而有趣的技术来源于多态与模板的结合,特别是所谓的types擦除

从C ++模板完整指南:

成员函数模板不能被声明为虚拟的。 这个限制是强加的,因为虚拟函数调用机制的通常实现使用每个虚函数具有一个条目的固定大小的表。 但是,在整个程序翻译之前,成员函数模板的实例化数量是不固定的。 因此,支持虚拟成员函数模板需要支持C ++编译器和链接器中的一种全新的机制。 相比之下,类模板的普通成员可以是虚拟的,因为当一个类被实例化时,它的数量是固定的

C ++现在不允许虚拟模板成员函数。 最可能的原因是实施它的复杂性。 拉金德拉给出了很好的理由,为什么现在不能做到这一点,但可以通过标准的合理变化来实现。 特别是计算一个模板函数实际存在多less个实例,如果考虑到虚函数调用的地方,构build虚表的难度似乎很难。 标准人员现在还有很多其他的事情要做,而C ++ 1x对于编译器编写者来说也是很多工作。

你什么时候需要一个模板化的成员函数? 我曾经遇到过这样的情况,我试图用纯虚拟基类来重构层次结构。 实施不同的策略是一种糟糕的风格。 我想改变其中一个虚拟函数的参数为​​一个数字types,而不是重载成员函数,并重写所有子类中的每个重载,我尝试使用虚拟模板函数(并且必须找出它们不存在。)

虚拟function表

让我们从虚拟function表的一些背景开始,以及它们如何工作( 来源 ):

[20.3]如何调用虚拟和非虚拟成员函数有什么区别?

非虚拟成员函数静态parsing。 也就是说,成员函数是根据指向对象的指针(或引用)的types静态地select的(在编译时)。

相反,虚拟成员函数是dynamicparsing的(在运行时)。 也就是说,成员函数根据对象的typesdynamicselect(运行时),而不是指向该对象的指针/引用的types。 这被称为“dynamic绑定”。 大多数编译器使用以下技术的一些变体:如果对象具有一个或多个虚拟函数,则编译器将一个隐藏指针放在对象中,称为“虚拟指针”或“V指针”。 这个v指针指向一个称为“虚拟表”或“v表”的全局表。

编译器为每个具有至less一个虚函数的类创build一个v表。 例如,如果Circle类具有draw(),move()和resize()的虚函数,那么即使存在一个巨大的Circle对象,也只有一个与Circle类关联的V表,每个Circle对象都会指向Circle v-table。 v表本身具有指向类中每个虚函数的指针。 例如,Circle v-table将有三个指针:一个指向Circle :: draw()的指针,一个指向Circle :: move()的指针以及一个指向Circle :: resize()的指针。

在分派虚拟函数的过程中,运行时系统跟随对象的v指针指向类的v表,然后跟随v表中相应的槽到方法代码。

上述技术的空间开销是名义上的:每个对象有一个额外的指针(但是仅用于需要做dynamic绑定的对象),每个方法加上一个额外的指针(但仅限于虚拟方法)。 时间 – 开销也是相当有名的:与一个正常的函数调用相比,一个虚函数调用需要两次额外的读取(一个获取v指针的值,第二个获取方法的地址)。 由于编译器会根据指针的types在编译时专门parsing非虚函数,因此这些运行时活动都不会发生在非虚函数上。


我的问题,或者我是如何来到这里的

我试图使用这样的东西,这样的立方体文件基类与模板优化的加载function,这将不同types的立方体(一些存储像素,一些图像等)不同的实施。

一些代码:

 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; 

我最终将模板声明移到了课程级别 。 这个解决scheme将迫使程序在读取之前知道他们要读取的特定types的数据,这是不可接受的。

警告,这不是很漂亮,但它允许我删除重复的执行代码

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)和儿童class

 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); 

请注意,LoadAnyCube没有在基类中声明。


这是另一个堆栈溢出的解决办法 : 需要一个虚拟模板成员解决方法 。

下面的代码可以在Window 7上使用MinGW G ++ 3.4.5进行编译和运行:

 #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&)' 

所以显而易见的是:

  • 虚拟成员函数可以在类模板中使用。 编译器很容易构buildvtable
  • 如你所见,定义一个类模板成员函数是不可能的,它很难确定函数签名和分配vtable条目。

不,他们不能。 但:

 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); }; 

如果你想要做的是有一个共同的接口,推迟到子类的实现有很多相同的效果。

回答问题的第二部分:

如果它们可以是虚拟的,那么可以使用这种function的场景的例子是什么?

这不是一个不合理的事情要做。 例如,Java(每个方法都是虚拟的)对于generics方法没有问题。

C ++中需要虚函数模板的一个例子是接受generics迭代器的成员函数。 或者是一个接受通用函数对象的成员函数。

解决这个问题的方法是使用types擦除与boost :: any_range和boost :: function,这将允许您接受一个generics迭代器或仿函数,而无需使您的function模板。

不,模板成员函数不能是虚拟的。

至less使用gcc 5.4虚函数可以是模板成员,但必须是模板本身。

 #include <iostream> #include <string> class first { protected: virtual std::string a1() { return "a1"; } virtual std::string mixt() { return a1(); } }; class last { protected: virtual std::string a2() { return "a2"; } }; template<class T> class mix: first , T { public: virtual std::string mixt() override; }; template<class T> std::string mix<T>::mixt() { return a1()+" before "+T::a2(); } class mix2: public mix<last> { virtual std::string a1() override { return "mix"; } }; int main() { std::cout << mix2().mixt(); return 0; } 

输出

 mix before a2 Process finished with exit code 0 

如果预先知道模板方法的types,则有一种“虚拟模板方法”的解决方法。

为了显示这个想法,在下面的例子中只使用了两种types( intdouble )。

在那里,一个“虚拟”模板方法( Base::Method )调用相应的虚拟方法( Base::VMethod ),然后调用模板方法实现( Impl::TMethod )。

只需要在派生实现( AImplBImpl )中实现模板方法TMethod并使用Derived<*Impl>

 class Base { public: virtual ~Base() { } template <typename T> T Method(T t) { return VMethod(t); } private: virtual int VMethod(int t) = 0; virtual double VMethod(double t) = 0; }; template <class Impl> class Derived : public Impl { public: template <class... TArgs> Derived(TArgs&&... args) : Impl(std::forward<TArgs>(args)...) { } private: int VMethod(int t) final { return Impl::TMethod(t); } double VMethod(double t) final { return Impl::TMethod(t); } }; class AImpl : public Base { protected: AImpl(int p) : i(p) { } template <typename T> T TMethod(T t) { return t - i; } private: int i; }; using A = Derived<AImpl>; class BImpl : public Base { protected: BImpl(int p) : i(p) { } template <typename T> T TMethod(T t) { return t + i; } private: int i; }; using B = Derived<BImpl>; int main(int argc, const char* argv[]) { A a(1); B b(1); Base* base = nullptr; base = &a; std::cout << base->Method(1) << std::endl; std::cout << base->Method(2.0) << std::endl; base = &b; std::cout << base->Method(1) << std::endl; std::cout << base->Method(2.0) << std::endl; } 

输出:

 0 1 2 3 

注意: Base::Method实际上是实际代码的剩余( VMethod方法可以公开并直接使用)。 我添加它,所以它看起来像一个实际的“虚拟”模板方法。