我听说C ++类成员函数模板不能是虚拟的。这是真的?
如果它们可以是虚拟的,那么使用这种功能的场景的例子是什么?
我听说C ++类成员函数模板不能是虚拟的。这是真的?
如果它们可以是虚拟的,那么使用这种功能的场景的例子是什么?
Answers:
模板都是关于编译器在编译时生成代码的。虚拟函数都是关于运行时系统的,找出要在运行时调用哪个函数。
一旦确定了运行时系统,它将需要调用模板化的虚拟函数,编译已全部完成,并且编译器无法再生成适当的实例。因此,您不能具有虚拟成员功能模板。
但是,结合多态性和模板可以使用一些强大而有趣的技术,特别是所谓的类型擦除。
Virtual functions are all about the run-time system figuring out which function to call at run-time
-很抱歉,但这是一种相当错误的方式,并且令人困惑。只是间接的,没有“运行时计算”,在编译时就知道要调用的函数是vtable中第n个指针指向的函数。“搞清楚”意味着存在类型检查,事实并非如此。Once the run-time system figured out it would need to call a templatized virtual function
-在编译时就知道函数是否为虚函数。
void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }
,则它“知道”在调用点调用了哪个函数cb.f()
,而对于则不知道vb.f()
。后者已被发现在运行时,运行时系统。您是否要称其为“弄清楚”,或者说效率更高或更低,都不会改变这些事实。
C ++目前不允许虚拟模板成员函数。最可能的原因是实施它的复杂性。拉金德拉(Rajendra)有充分的理由说明为什么现在无法完成,但是只要合理更改标准,它就有可能实现。如果考虑虚拟函数调用的位置,特别是要计算出实际上存在多少个模板化函数实例并建立vtable似乎很困难。标准人员现在还有很多其他事情要做,C ++ 1x对于编译器编写者来说也是很多工作。
什么时候需要模板成员函数?我曾经遇到过这样的情况,我试图用纯虚拟基类重构层次结构。实施不同策略的风格很差。我想将其中一个虚拟函数的参数更改为数值类型,而不是重载成员函数并覆盖所有试图使用虚拟模板函数的所有子类中的每个重载(并且不得不发现它们不存在) )
让我们从虚拟函数表及其工作原理(源)的一些背景开始:
[20.3]如何调用虚拟和非虚拟成员函数之间的区别?
非虚拟成员函数是静态解析的。即,基于指向对象的指针(或引用)的类型来静态地(在编译时)选择成员函数。
相反,虚拟成员函数是动态解析的(在运行时)。也就是说,成员函数是根据对象的类型而不是指针/对该对象的引用的类型(在运行时)动态选择的。这称为“动态绑定”。大多数编译器使用以下技术的某种变体:如果对象具有一个或多个虚拟函数,则编译器会在对象中放置一个隐藏的指针,称为“虚拟指针”或“ v指针”。该v指针指向一个称为“虚拟表”或“ v表”的全局表。
编译器为具有至少一个虚函数的每个类创建一个v表。例如,如果Circle类具有针对draw()和move()以及resize()的虚函数,则即使有成千上万个Circle对象,也将只有一个与Circle类关联的v表。每个Circle对象将指向Circle v表。v表本身具有指向类中每个虚拟函数的指针。例如,Circle 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);
请注意,LoadAnyCube没有在基类中声明。
这是另一个解决方法的堆栈溢出答案: 需要一个虚拟模板成员解决方法。
在Windows 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&)'
因此很明显:
不,他们不能。但:
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>
。
不可以,模板成员函数不能是虚拟的。
在其他答案中,建议的模板功能是外观,没有任何实际好处。
该语言不允许虚拟模板功能,但是有一种解决方法,可以同时使用这两种语言,例如,每个类都有一个模板实现和一个虚拟公共接口。
但是,必须为每种模板类型组合定义一个虚拟虚包装函数:
#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
在这里尝试
要回答问题的第二部分:
如果它们可以是虚拟的,那么使用这种功能的场景的例子是什么?
这不是要做的不合理的事情。例如,Java(每种方法都是虚拟的)对通用方法没有任何问题。
C ++中需要虚拟函数模板的一个示例是接受通用迭代器的成员函数。或接受通用函数对象的成员函数。
解决此问题的方法是将类型擦除与boost :: any_range和boost :: function一起使用,这将允许您接受通用的迭代器或仿函数,而无需将函数设为模板。
如果事先知道模板方法的类型集,则有一种“虚拟模板方法”的解决方法。
为了说明这一点,在下面的示例中,仅使用两种类型(int
和double
)。
在这里,“虚拟”模板方法(Base::Method
)调用了相应的虚拟方法(之一Base::VMethod
),而虚拟方法又调用了模板方法实现(Impl::TMethod
)。
只需TMethod
在派生的实现(AImpl
,BImpl
)和use中实现模板方法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
可以公开并直接使用)。我添加了它,使它看起来像是一种实际的“虚拟”模板方法。
Base
每次需要调用参数类型与到目前为止实现的参数类型不兼容的模板函数时都必须修改原始类的事实。模板的目的是避免这种必要……
尽管我认为许多人已经回答了一个较老的问题,但我认为一种简洁的方法(与发布的其他方法没有什么不同)是使用次要宏来帮助简化类声明的重复。
// 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
};
这样做的好处是,当添加新支持的类型时,都可以从抽象标头中完成所有操作,并且可以放弃在多个源/标头文件中对其进行纠正。
至少对于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
尝试这个:
在classeder.h中编写:
template <typename T>
class Example{
public:
T c_value;
Example(){}
T Set(T variable)
{
return variable;
}
virtual Example VirtualFunc(Example paraM)
{
return paraM.Set(c_value);
}
检查是否可以在main.cpp中编写以下代码:
#include <iostream>
#include <classeder.h>
int main()
{
Example exmpl;
exmpl.c_value = "Hello, world!";
std::cout << exmpl.VirtualFunc(exmpl);
return 0;
}