我听说C类成员函数模板不能是虚拟的 . 这是真的?
如果它们可以是虚拟的,那么一个人可以使用这样一个函数的场景的例子是什么?
模板是关于在 compile-time 生成代码的编译器 . 虚函数是关于运行时系统确定在 run-time 调用哪个函数 .
一旦运行时系统发现它需要调用一个模板化的虚函数,编译就完成了,编译器就不能再生成适当的实例了 . 因此,您不能拥有虚拟成员函数模板 .
然而,有一些强大而有趣的技术源于结合多态性和模板,特别是所谓的type erasure .
来自C模板完整指南:
无法将成员函数模板声明为虚拟 . 强加此约束是因为虚函数调用机制的通常实现使用固定大小的表,每个虚函数有一个条目 . 但是,在翻译整个程序之前,成员函数模板的实例化数量不固定 . 因此,支持虚拟成员函数模板需要支持C编译器和链接器中的全新机制 . 相反,类模板的普通成员可以是虚拟的,因为在实例化类时它们的数量是固定的
C现在不允许虚拟模板成员功能 . 最可能的原因是实施它的复杂性 . 拉金德拉很好地说明了为什么现在无法做到这一点,但可以通过合理的标准变更来实现 . 特别是如果你考虑虚函数调用的位置,那么实际存在模板函数的实例数并且构建vtable似乎很困难 . 标准人员现在还有许多其他事情要做,而C 1x也是编译器编写者的大量工作 .
你什么时候需要模板成员函数?我曾经遇到过这种情况,我试图用纯虚基类重构层次结构 . 实施不同的策略是一种糟糕的风格 . 我想将其中一个虚函数的参数更改为数字类型而不是重载成员函数并覆盖我尝试使用虚拟模板函数的所有子类中的每个重载(并且必须发现它们不存在 . )
让我们从虚拟函数表及其工作原理的一些背景知识开始(source):
[20.3]虚拟和非虚拟成员函数的调用方式有何区别?非虚拟成员函数是静态解析的 . 也就是说,基于对象的指针(或引用)的类型静态地(在编译时)选择成员函数 . 相反,虚拟成员函数是动态解析的(在运行时) . 也就是说,基于对象的类型动态地(在运行时)选择成员函数,而不是指向该对象的指针/引用的类型 . 这称为“动态绑定” . 大多数编译器使用以下技术的一些变体:如果对象具有一个或多个虚函数,则编译器会在对象中放置一个名为“虚拟指针”或“v指针”的隐藏指针 . 此v指针指向称为“虚拟表”或“v表”的全局表 . 编译器为每个具有至少一个虚函数的类创建一个v表 . 例如,如果类Circle具有draw()和move()以及resize()的虚函数,则只有一个v-table与类Circle相关联,即使存在大量的Circle对象,并且v指针也是如此 . 每个Circle对象都指向Circle v表 . v表本身具有指向类中每个虚函数的指针 . 例如,Circle v-table将有三个指针:指向Circle :: draw()的指针,指向Circle :: move()的指针,以及指向Circle :: resize()的指针 . 在调度虚函数期间,运行时系统遵循对象的v指针到类的v表,然后在v表中的相应插槽跟随方法代码 . 上述技术的空间成本开销是标称的:每个对象一个额外的指针(但仅适用于需要进行动态绑定的对象),以及每个方法的额外指针(但仅适用于虚方法) . 该时间成本开销也是相当标准的:与普通函数调用相比,虚函数调用需要两次额外的提取(一次获取v指针的值,第二次获取方法的地址) . 非虚函数不会发生此运行时活动,因为编译器会根据指针类型在编译时专门解析非虚函数 .
我正在尝试使用类似这样的东西,用于具有模板化优化加载函数的cubefile基类,对于不同类型的多维数据集(一些按像素存储,一些按图像存储等)将以不同方式实现 .
一些代码:
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)在基类中
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);
请注意,LoadAnyCube未在基类中声明 .
这是另一个堆栈溢出答案,解决方法:need a virtual template member workaround .
在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&)'
所以很明显:
虚拟成员函数可以在类模板中使用 . 编译器很容易构造vtable
无法将类模板成员函数定义为虚拟,如您所见,很难确定函数签名并分配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); };
如果您只想拥有一个通用接口并将实现推迟到子类,则会产生相同的效果 .
要回答问题的第二部分:
如果它们可以是虚拟的,那么使用这样的函数的场景的示例是什么?
这不是一件不合理的事情 . 例如,Java(每个方法都是虚方法)对泛型方法没有任何问题 .
想要虚函数模板的C中的一个例子是接受泛型迭代器的成员函数 . 或者是接受通用函数对象的成员函数 .
这个问题的解决方案是使用类型擦除与boost :: any_range和boost :: function,这将允许您接受通用迭代器或函子,而无需使您的函数成为模板 .
不,模板成员函数不能是虚拟的 .
如果事先知道模板方法的类型集,则有“虚拟模板方法”的解决方法 .
为了显示这个想法,在下面的例子中只使用了两种类型( int 和 double ) .
int
double
在那里,'virtual'模板方法( Base::Method )调用相应的虚方法( Base::VMethod 之一),而后者又调用模板方法实现( Impl::TMethod ) .
Base::Method
Base::VMethod
Impl::TMethod
只需要在派生实现( AImpl , BImpl )中实现模板方法 TMethod 并使用 Derived<*Impl> .
AImpl
BImpl
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 可以公开并直接使用) . 我添加它,所以它看起来像一个真正的'virtual'模板方法 .
VMethod
至少使用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
在其他答案中,建议的模板功能是一个外观,并没有提供任何实际的好处 .
模板函数仅对使用不同类型编写代码一次非常有用 .
虚函数对于为不同的类具有公共接口很有用 .
该语言不允许使用虚拟模板功能,但通过一种解决方法,可以同时使用这两种功能 . 每个类的一个模板实现和一个虚拟公共接口 .
但是,有必要为每个模板类型组合定义一个虚拟虚拟包装函数:
#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
试试吧here
11 回答
模板是关于在 compile-time 生成代码的编译器 . 虚函数是关于运行时系统确定在 run-time 调用哪个函数 .
一旦运行时系统发现它需要调用一个模板化的虚函数,编译就完成了,编译器就不能再生成适当的实例了 . 因此,您不能拥有虚拟成员函数模板 .
然而,有一些强大而有趣的技术源于结合多态性和模板,特别是所谓的type erasure .
来自C模板完整指南:
C现在不允许虚拟模板成员功能 . 最可能的原因是实施它的复杂性 . 拉金德拉很好地说明了为什么现在无法做到这一点,但可以通过合理的标准变更来实现 . 特别是如果你考虑虚函数调用的位置,那么实际存在模板函数的实例数并且构建vtable似乎很困难 . 标准人员现在还有许多其他事情要做,而C 1x也是编译器编写者的大量工作 .
你什么时候需要模板成员函数?我曾经遇到过这种情况,我试图用纯虚基类重构层次结构 . 实施不同的策略是一种糟糕的风格 . 我想将其中一个虚函数的参数更改为数字类型而不是重载成员函数并覆盖我尝试使用虚拟模板函数的所有子类中的每个重载(并且必须发现它们不存在 . )
虚函数表
让我们从虚拟函数表及其工作原理的一些背景知识开始(source):
我的问题,或者我是怎么来到这里的
我正在尝试使用类似这样的东西,用于具有模板化优化加载函数的cubefile基类,对于不同类型的多维数据集(一些按像素存储,一些按图像存储等)将以不同方式实现 .
一些代码:
我希望它是什么,但由于虚拟模板组合它将无法编译:
我最终将模板声明移到了类级别 . 该解决方案将迫使程序在读取之前了解它们将读取的特定类型的数据,这是不可接受的 .
解决方案
警告,这不是很漂亮,但它允许我删除重复的执行代码
1)在基类中
2)和儿童班
请注意,LoadAnyCube未在基类中声明 .
这是另一个堆栈溢出答案,解决方法:need a virtual template member workaround .
在Window 7上使用MinGW G 3.4.5可以编译和运行以下代码:
输出是:
后来我添加了一个新的类X:
当我尝试在main()中使用类X时,如下所示:
g报告以下错误:
所以很明显:
虚拟成员函数可以在类模板中使用 . 编译器很容易构造vtable
无法将类模板成员函数定义为虚拟,如您所见,很难确定函数签名并分配vtable条目 .
不,他们不能 . 但:
如果您只想拥有一个通用接口并将实现推迟到子类,则会产生相同的效果 .
要回答问题的第二部分:
这不是一件不合理的事情 . 例如,Java(每个方法都是虚方法)对泛型方法没有任何问题 .
想要虚函数模板的C中的一个例子是接受泛型迭代器的成员函数 . 或者是接受通用函数对象的成员函数 .
这个问题的解决方案是使用类型擦除与boost :: any_range和boost :: function,这将允许您接受通用迭代器或函子,而无需使您的函数成为模板 .
不,模板成员函数不能是虚拟的 .
如果事先知道模板方法的类型集,则有“虚拟模板方法”的解决方法 .
为了显示这个想法,在下面的例子中只使用了两种类型(
int
和double
) .在那里,'virtual'模板方法(
Base::Method
)调用相应的虚方法(Base::VMethod
之一),而后者又调用模板方法实现(Impl::TMethod
) .只需要在派生实现(
AImpl
,BImpl
)中实现模板方法TMethod
并使用Derived<*Impl>
.输出:
注意:
Base::Method
实际上是实际代码的剩余(VMethod
可以公开并直接使用) . 我添加它,所以它看起来像一个真正的'virtual'模板方法 .至少使用gcc 5.4虚函数可以是模板成员,但必须是模板本身 .
输出
在其他答案中,建议的模板功能是一个外观,并没有提供任何实际的好处 .
模板函数仅对使用不同类型编写代码一次非常有用 .
虚函数对于为不同的类具有公共接口很有用 .
该语言不允许使用虚拟模板功能,但通过一种解决方法,可以同时使用这两种功能 . 每个类的一个模板实现和一个虚拟公共接口 .
但是,有必要为每个模板类型组合定义一个虚拟虚拟包装函数:
输出:
试试吧here