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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2354210/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:11:14  来源:igfitidea点击:

Can a class member function template be virtual?

c++templatesvirtual-functionsfunction-templatesc++-faq

提问by WannaBeGeek

I have heard that C++ class member function templates can't be virtual. Is this true?

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

If they can be virtual, what is an example of a scenario in which one would use such a function?

如果它们可以是虚拟的,那么人们将使用这种功能的场景的示例是什么?

回答by sbi

Templates are all about the compiler generating code at compile-time. Virtual functions are all about the run-time system figuring out which function to call at run-time.

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

Once the run-time system figured out it would need to call a templatized virtual function, compilation is all done and the compiler cannot generate the appropriate instance anymore. Therefore you cannot have virtual member function templates.

一旦运行时系统发现它需要调用一个模板化的虚函数,编译就完成了,编译器不能再生成适当的实例。因此,您不能拥有虚拟成员函数模板。

However, there are a few powerful and interesting techniques stemming from combining polymorphism and templates, notably so-called type erasure.

然而,有一些强大而有趣的技术源于多态性和模板的结合,特别是所谓的类型擦除

回答by InQusitive

From C++ Templates The Complete Guide:

来自 C++ 模板的完整指南:

Member function templates cannot be declared virtual. This constraint is imposed because the usual implementation of the virtual function call mechanism uses a fixed-size table with one entry per virtual function. However, the number of instantiations of a member function template is not fixed until the entire program has been translated. Hence, supporting virtual member function templates would require support for a whole new kind of mechanism in C++ compilers and linkers. In contrast, the ordinary members of class templates can be virtual because their number is fixed when a class is instantiated

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

回答by pmr

C++ doesn't allow virtual template member functions right now. The most likely reason is the complexity of implementing it. Rajendra gives good reason why it can't be done right now but it could be possible with reasonable changes of the standard. Especially working out how many instantiations of a templated function actually exist and building up the vtable seems difficult if you consider the place of the virtual function call. Standards people just have a lot of other things to do right now and C++1x is a lot of work for the compiler writers as well.

C++ 现在不允许使用虚拟模板成员函数。最可能的原因是实现它的复杂性。Rajendra 给出了为什么现在不能完成的很好的理由,但可以通过对标准进行合理的更改来实现。如果考虑到虚函数调用的位置,特别是计算模板化函数实际存在的实例化数量和构建 vtable 似乎很困难。标准人员现在还有很多其他事情要做,而 C++1x 也是编译器编写者的大量工作。

When would you need a templated member function? I once came across such a situation where I tried to refactor a hierarchy with a pure virtual base class. It was a poor style for implementing different strategies. I wanted to change the argument of one of the virtual functions to a numeric type and instead of overloading the member function and override every overload in all sub-classes I tried to use virtual template functions (and had to find out they don't exist.)

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

回答by Mark Essel

Virtual Function Tables

虚函数表

Let's begin with some background on virtual function tables and how they work (source):

让我们从虚函数表及其工作原理的一些背景开始(来源):

[20.3] What's the difference between how virtual and non-virtual member functions are called?

Non-virtual member functions are resolved statically. That is, the member function is selected statically (at compile-time) based on the type of the pointer (or reference) to the object.

In contrast, virtual member functions are resolved dynamically (at run-time). That is, the member function is selected dynamically (at run-time) based on the type of the object, not the type of the pointer/reference to that object. This is called "dynamic binding." Most compilers use some variant of the following technique: if the object has one or more virtual functions, the compiler puts a hidden pointer in the object called a "virtual-pointer" or "v-pointer." This v-pointer points to a global table called the "virtual-table" or "v-table."

The compiler creates a v-table for each class that has at least one virtual function. For example, if class Circle has virtual functions for draw() and move() and resize(), there would be exactly one v-table associated with class Circle, even if there were a gazillion Circle objects, and the v-pointer of each of those Circle objects would point to the Circle v-table. The v-table itself has pointers to each of the virtual functions in the class. For example, the Circle v-table would have three pointers: a pointer to Circle::draw(), a pointer to Circle::move(), and a pointer to Circle::resize().

During a dispatch of a virtual function, the run-time system follows the object's v-pointer to the class's v-table, then follows the appropriate slot in the v-table to the method code.

The space-cost overhead of the above technique is nominal: an extra pointer per object (but only for objects that will need to do dynamic binding), plus an extra pointer per method (but only for virtual methods). The time-cost overhead is also fairly nominal: compared to a normal function call, a virtual function call requires two extra fetches (one to get the value of the v-pointer, a second to get the address of the method). None of this runtime activity happens with non-virtual functions, since the compiler resolves non-virtual functions exclusively at compile-time based on the type of the pointer.

[20.3] 虚成员函数和非虚成员函数的调用方式有什么区别?

非虚拟成员函数是静态解析的。也就是说,成员函数是根据指向对象的指针(或引用)的类型静态(在编译时)选择的。

相反,虚拟成员函数是动态解析的(在运行时)。也就是说,成员函数是根据对象的类型(在运行时)动态选择的,而不是根据指向该对象的指针/引用的类型。这称为“动态绑定”。大多数编译器使用以下技术的一些变体:如果对象具有一个或多个虚函数,编译器会在对象中放置一个隐藏的指针,称为“虚拟指针”或“v 指针”。这个 v 指针指向一个称为“虚拟表”或“v 表”的全局表。

编译器为每个至少有一个虚函数的类创建一个 v-table。例如,如果 Circle 类具有用于 draw() 和 move() 以及 resize() 的虚函数,那么即使有无数 Circle 对象,也恰好有一个与 Circle 相关联的 v-table 和 v-pointer这些 Circle 对象中的每一个都将指向 Circle v-table。v-table 本身有指向类中每个虚函数的指针。例如,Circle v-table 将具有三个指针:指向 Circle::draw() 的指针、指向 Circle::move() 的指针和指向 Circle::resize() 的指针。

在分配虚函数期间,运行时系统遵循对象的 v 指针指向类的 v 表,然后遵循 v 表中适当的槽指向方法代码。

上述技术的空间成本开销是名义上的:每个对象一个额外的指针(但仅限于需要进行动态绑定的对象),每个方法加上一个额外的指针(但仅限于虚拟方法)。时间成本开销也相当小:与普通函数调用相比,虚函数调用需要两次额外的获取(一次获取 v 指针的值,另一次获取方法的地址)。非虚拟函数不会发生此运行时活动,因为编译器仅在编译时根据指针类型解析非虚拟函数。



My problem, or how I came here

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

I'm attempting to use something like this now for a cubefile base class with templated optimized load functions which will be implemented differently for different types of cubes (some stored by pixel, some by image, etc).

我现在正在尝试将这样的东西用于带有模板化优化加载函数的立方体文件基类,这些函数将针对不同类型的立方体(一些按像素存储,一些按图像等)实现不同。

Some code:

一些代码:

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;

What I'd like it to be, but it won't compile due to a virtual templated combo:

我希望它是什么,但由于虚拟模板组合而无法编译:

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;

I ended up moving the template declaration to the class level. This solution would have forced programs to know about specific types of data they would read before they read them, which is unacceptable.

我最终将模板声明移到了类级别。这种解决方案将迫使程序在读取数据之前知道它们将读取的特定类型的数据,这是不可接受的。

Solution

解决方案

warning, this isn't very pretty but it allowed me to remove repetitive execution code

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

1) in the base class

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) and in the child classes

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

Note that LoadAnyCube is not declared in the base class.

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



Here's another stack overflow answer with a work around: need a virtual template member workaround.

这是另一个带有变通方法的堆栈溢出答案: 需要一个虚拟模板成员变通方法

回答by Brent81

The following code can be compiled and runs properly, using MinGW G++ 3.4.5 on Window 7:

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

and the output is:

输出是:

A:A<string> a
A<--B:B<string> c
A<--B:3

And later I added a new class X:

后来我添加了一个新的类 X:

class X
{
public:
    template <typename T>
    virtual void func2(const T& p)
    {
        cout<<"C:"<<p<<endl;
    }
};

When I tried to use class X in main() like this:

当我尝试在 main() 中使用类 X 时:

X x;
x.func2<string>("X x");

g++ report the following error:

g++报如下错误:

vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'

So it is obvious that:

所以很明显:

  • virtual member function can be used in a class template. It is easy for compiler to construct vtable
  • It is impossible to define a class template member function as virtual, as you can see, it hard to determine function signature and allocate vtable entries.
  • 虚拟成员函数可以在类模板中使用。编译器很容易构造vtable
  • 将类模板成员函数定义为虚拟是不可能的,如您所见,很难确定函数签名和分配 vtable 条目。

回答by Tom

No they can't. But:

不,他们不能。但:

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

has much the same effect if all you want to do is have a common interface and defer implementation to subclasses.

如果您只想拥有一个公共接口并将实现推迟到子类,则具有大致相同的效果。

回答by dirkgently

No, template member functions cannot be virtual.

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

回答by andreaplanet

In the other answers the proposed template function is a facade and doesn't offer any practical benefit.

在其他答案中,建议的模板函数是一个外观,并没有提供任何实际好处。

  • Template functions are useful for writing code only once using different types.
  • Virtual functions are useful for having a common interface for different classes.
  • 模板函数对于使用不同类型只编写一次代码很有用。
  • 虚拟函数对于为不同的类拥有一个公共接口很有用。

The language doesn't allow virtual template functions but with a workaround it is possible to have both, e.g. one template implementation for each class and a virtual common interface.

该语言不允许使用虚拟模板函数,但通过一种变通方法,可以同时拥有两者,例如每个类的一个模板实现和一个虚拟公共接口。

It is however necessary to define for each template type combination a dummy virtual wrapper function:

然而,有必要为每个模板类型组合定义一个虚拟的虚拟包装器函数:

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

Output:

输出:

Square area is 1, Circle area is 3.1415926535897932385

正方形面积为1,圆面积为3.1415926535897932385

Try it here

在这里试试

回答by exclipy

To answer the second part of the question:

回答问题的第二部分:

If they can be virtual, what is an example of a scenario in which one would use such a function?

如果它们可以是虚拟的,那么人们将使用这种功能的场景的示例是什么?

This is not an unreasonable thing to want to do. For instance, Java (where every method is virtual) has no problems with generic methods.

这不是一件不合理的事情。例如,Java(其中每个方法都是虚拟的)对泛型方法没有问题。

One example in C++ of wanting a virtual function template is a member function that accepts a generic iterator. Or a member function that accepts a generic function object.

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

The solution to this problem is to use type erasure with boost::any_range and boost::function, which will allow you to accept a generic iterator or functor without the need to make your function a template.

这个问题的解决方案是在 boost::any_range 和 boost::function 中使用类型擦除,这将允许您接受通用迭代器或函子,而无需将您的函数设为模板。

回答by mccatnm

While an older question that has been answered by many I believe a succinct method, not so different from the others posted, is to use a minor macro to help ease the duplication of class declarations.

虽然许多人已经回答了一个较旧的问题,但我相信一个简洁的方法,与其他发布的没有太大区别,是使用一个小宏来帮助缓解类声明的重复。

// 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;
    // ...
};

So now, to implement our subclass:

所以现在,要实现我们的子类:

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

The benefit here is that, when adding a newly supported type, it can all be done from the abstract header and forego possibly rectifying it in multiple source/header files.

这样做的好处是,当添加新支持的类型时,所有这些都可以从抽象头文件中完成,并且可能会放弃在多个源/头文件中对其进行纠正。