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

发布于 2024-08-24 02:19:58 字数 70 浏览 8 评论 0原文

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

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

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

悲念泪 2024-08-31 02:19:59

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

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

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

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.

傲影 2024-08-31 02:19:59

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

成员函数模板不能声明为虚拟的。这个约束
是因为虚函数的通常实现而强加的
调用机制使用固定大小的表,每个虚拟有一个条目
功能。然而,成员函数的实例化数量
直到整个程序翻译完成后,模板才得以确定。
因此,支持虚拟成员函数模板需要
支持 C++ 编译器中的一种全新机制
连接器。相反,类模板的普通成员可以是
virtual,因为它们的数量在实例化类时是固定的

From C++ Templates The Complete Guide:

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

雾里花 2024-08-31 02:19:59

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

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

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.

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

她如夕阳 2024-08-31 02:19:59

虚拟函数表

让我们首先了解一些有关虚拟函数表的背景知识及其工作原理 (来源):

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

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

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

编译器为每个至少有一个类的类创建一个 v 表
虚函数。例如,如果类 Circle 有虚函数
对于draw()、move()和resize(),只有一个v表
与 Circle 类相关联,即使有无数个 Circle
对象,并且每个 Circle 对象的 v 指针将指向
到 Circle v 表。 v表本身有指向每个的指针
类中的虚函数。例如,Circle v-table 将
有三个指针:一个指向 Circle::draw() 的指针,一个指向
Circle::move() 和一个指向 Circle::resize() 的指针。

在分派虚拟函数期间,运行时系统遵循
对象的 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 未在基类中声明。


这是另一个堆栈溢出答案和解决方法:
​​需要虚拟模板成员解决方法

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.


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

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

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.


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

你与昨日 2024-08-31 02:19:59

不,他们不能。但是:

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

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

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.

感性不性感 2024-08-31 02:19:59

在 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 将
  • 类模板成员函数定义为 virtual 是不可能的,正如您所看到的,很难确定函数签名和分配 vtable 条目。

The following code can be compiled and runs properly, using MinGW G++ 3.4.5 on Window 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);
}

and the output is:

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

And later I added a new class 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:

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

g++ report the following error:

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.
_畞蕅 2024-08-31 02:19:59

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

No, template member functions cannot be virtual.

硬不硬你别怂 2024-08-31 02:19:59

在其他答案中,建议的模板功能是一个外观,不提供任何实际好处。

  • 模板函数对于仅使用一次编写代码很有用
    不同类型。
  • 虚函数对于为不同的类提供通用接口非常有用。

该语言不允许虚拟模板函数,但通过一种解决方法,可以同时拥有这两种函数,例如,每个类都有一个模板实现,并且有一个虚拟公共接口。

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

#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

试试此处

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

Try it here

弥枳 2024-08-31 02:19:59

回答问题的第二部分:

如果它们可以是虚拟的,使用此类功能的场景示例是什么?

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

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

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

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.

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.

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.

2024-08-31 02:19:59

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

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

这里的好处是,当添加新支持的类型时,这一切都可以从抽象标头完成,并且不必在多个源/标头文件中对其进行纠正。

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.

窗影残 2024-08-31 02:19:59

如果提前知道模板方法的类型集,则有“虚拟模板方法”的解决方法。

为了展示这个想法,在下面的示例中仅使用了两种类型(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可以公开直接使用)。
我添加了它,因此它看起来像一个实际的“虚拟”模板方法。

There is a workaround for 'virtual template method' if set of types for the template method is known in advance.

To show the idea, in the example below only two types are used (int and double).

There, a 'virtual' template method (Base::Method) calls corresponding virtual method (one of Base::VMethod) which, in turn, calls template method implementation (Impl::TMethod).

One only needs to implement template method TMethod in derived implementations (AImpl, BImpl) and 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;
}

Output:

0
1
2
3

NB:
Base::Method is actually surplus for real code (VMethod can be made public and used directly).
I added it so it looks like as an actual 'virtual' template method.

背叛残局 2024-08-31 02:19:59

至少在 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

At least with gcc 5.4 virtual functions could be template members but has to be templates themselves.

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

Outputs

mix before a2
Process finished with exit code 0
心作怪 2024-08-31 02:19:59

我当前的解决方案如下(禁用 RTTI - 您也可以使用 std::type_index):

#include <type_traits>
#include <iostream>
#include <tuple>

class Type
{
};

template<typename T>
class TypeImpl : public Type
{

};

template<typename T>
inline Type* typeOf() {
    static Type* typePtr = new TypeImpl<T>();
    return typePtr;
}

/* ------------- */

template<
    typename Calling
    , typename Result = void
    , typename From
    , typename Action
>
inline Result DoComplexDispatch(From* from, Action&& action);

template<typename Cls>
class ChildClasses
{
public:
    using type = std::tuple<>;
};

template<typename... Childs>
class ChildClassesHelper
{
public:
    using type = std::tuple<Childs...>;
};

//--------------------------

class A;
class B;
class C;
class D;

template<>
class ChildClasses<A> : public ChildClassesHelper<B, C, D> {};

template<>
class ChildClasses<B> : public ChildClassesHelper<C, D> {};

template<>
class ChildClasses<C> : public ChildClassesHelper<D> {};

//-------------------------------------------

class A
{
public:
    virtual Type* GetType()
    {
        return typeOf<A>();
    }

    template<
        typename T,
        bool checkType = true
    >
        /*virtual*/void DoVirtualGeneric()
    {
        if constexpr (checkType)
        {
            return DoComplexDispatch<A>(this, [&](auto* other) -> decltype(auto)
                {
                    return other->template DoVirtualGeneric<T, false>();
                });
        }
        std::cout << "A";
    }
};

class B : public A
{
public:
    virtual Type* GetType()
    {
        return typeOf<B>();
    }
    template<
        typename T,
        bool checkType = true
    >
    /*virtual*/void DoVirtualGeneric() /*override*/
    {
        if constexpr (checkType)
        {
            return DoComplexDispatch<B>(this, [&](auto* other) -> decltype(auto)
                {
                    other->template DoVirtualGeneric<T, false>();
                });
        }
        std::cout << "B";
    }
};

class C : public B
{
public:
    virtual Type* GetType() {
        return typeOf<C>();
    }

    template<
        typename T,
        bool checkType = true
    >
    /*virtual*/void DoVirtualGeneric() /*override*/
    {
        if constexpr (checkType)
        {
            return DoComplexDispatch<C>(this, [&](auto* other) -> decltype(auto)
                {
                    other->template DoVirtualGeneric<T, false>();
                });
        }
        std::cout << "C";
    }
};

class D : public C
{
public:
    virtual Type* GetType() {
        return typeOf<D>();
    }
};

int main()
{
    A* a = new A();
    a->DoVirtualGeneric<int>();
}

// --------------------------

template<typename Tuple>
class RestTuple {};

template<
    template<typename...> typename Tuple,
    typename First,
    typename... Rest
>
class RestTuple<Tuple<First, Rest...>> {
public:
    using type = Tuple<Rest...>;
};

// -------------
template<
    typename CandidatesTuple
    , typename Result
    , typename From
    , typename Action
>
inline constexpr Result DoComplexDispatchInternal(From* from, Action&& action, Type* fromType)
{
    using FirstCandidate = std::tuple_element_t<0, CandidatesTuple>;

    if constexpr (std::tuple_size_v<CandidatesTuple> == 1)
    {
        return action(static_cast<FirstCandidate*>(from));
    }
    else {
        if (fromType == typeOf<FirstCandidate>())
        {
            return action(static_cast<FirstCandidate*>(from));
        }
        else {
            return DoComplexDispatchInternal<typename RestTuple<CandidatesTuple>::type, Result>(
                from, action, fromType
            );
        }
    }
}

template<
    typename Calling
    , typename Result
    , typename From
    , typename Action
>
inline Result DoComplexDispatch(From* from, Action&& action)
{
    using ChildsOfCalling = typename ChildClasses<Calling>::type;
    if constexpr (std::tuple_size_v<ChildsOfCalling> == 0)
    {
        return action(static_cast<Calling*>(from));
    }
    else {
        auto fromType = from->GetType();
        using Candidates = decltype(std::tuple_cat(std::declval<std::tuple<Calling>>(), std::declval<ChildsOfCalling>()));
        return DoComplexDispatchInternal<Candidates, Result>(
            from, std::forward<Action>(action), fromType
        );
    }
}

我唯一不喜欢的是您必须定义/注册所有子类。

My current solution is the following (with RTTI disabled - you could use std::type_index, too):

#include <type_traits>
#include <iostream>
#include <tuple>

class Type
{
};

template<typename T>
class TypeImpl : public Type
{

};

template<typename T>
inline Type* typeOf() {
    static Type* typePtr = new TypeImpl<T>();
    return typePtr;
}

/* ------------- */

template<
    typename Calling
    , typename Result = void
    , typename From
    , typename Action
>
inline Result DoComplexDispatch(From* from, Action&& action);

template<typename Cls>
class ChildClasses
{
public:
    using type = std::tuple<>;
};

template<typename... Childs>
class ChildClassesHelper
{
public:
    using type = std::tuple<Childs...>;
};

//--------------------------

class A;
class B;
class C;
class D;

template<>
class ChildClasses<A> : public ChildClassesHelper<B, C, D> {};

template<>
class ChildClasses<B> : public ChildClassesHelper<C, D> {};

template<>
class ChildClasses<C> : public ChildClassesHelper<D> {};

//-------------------------------------------

class A
{
public:
    virtual Type* GetType()
    {
        return typeOf<A>();
    }

    template<
        typename T,
        bool checkType = true
    >
        /*virtual*/void DoVirtualGeneric()
    {
        if constexpr (checkType)
        {
            return DoComplexDispatch<A>(this, [&](auto* other) -> decltype(auto)
                {
                    return other->template DoVirtualGeneric<T, false>();
                });
        }
        std::cout << "A";
    }
};

class B : public A
{
public:
    virtual Type* GetType()
    {
        return typeOf<B>();
    }
    template<
        typename T,
        bool checkType = true
    >
    /*virtual*/void DoVirtualGeneric() /*override*/
    {
        if constexpr (checkType)
        {
            return DoComplexDispatch<B>(this, [&](auto* other) -> decltype(auto)
                {
                    other->template DoVirtualGeneric<T, false>();
                });
        }
        std::cout << "B";
    }
};

class C : public B
{
public:
    virtual Type* GetType() {
        return typeOf<C>();
    }

    template<
        typename T,
        bool checkType = true
    >
    /*virtual*/void DoVirtualGeneric() /*override*/
    {
        if constexpr (checkType)
        {
            return DoComplexDispatch<C>(this, [&](auto* other) -> decltype(auto)
                {
                    other->template DoVirtualGeneric<T, false>();
                });
        }
        std::cout << "C";
    }
};

class D : public C
{
public:
    virtual Type* GetType() {
        return typeOf<D>();
    }
};

int main()
{
    A* a = new A();
    a->DoVirtualGeneric<int>();
}

// --------------------------

template<typename Tuple>
class RestTuple {};

template<
    template<typename...> typename Tuple,
    typename First,
    typename... Rest
>
class RestTuple<Tuple<First, Rest...>> {
public:
    using type = Tuple<Rest...>;
};

// -------------
template<
    typename CandidatesTuple
    , typename Result
    , typename From
    , typename Action
>
inline constexpr Result DoComplexDispatchInternal(From* from, Action&& action, Type* fromType)
{
    using FirstCandidate = std::tuple_element_t<0, CandidatesTuple>;

    if constexpr (std::tuple_size_v<CandidatesTuple> == 1)
    {
        return action(static_cast<FirstCandidate*>(from));
    }
    else {
        if (fromType == typeOf<FirstCandidate>())
        {
            return action(static_cast<FirstCandidate*>(from));
        }
        else {
            return DoComplexDispatchInternal<typename RestTuple<CandidatesTuple>::type, Result>(
                from, action, fromType
            );
        }
    }
}

template<
    typename Calling
    , typename Result
    , typename From
    , typename Action
>
inline Result DoComplexDispatch(From* from, Action&& action)
{
    using ChildsOfCalling = typename ChildClasses<Calling>::type;
    if constexpr (std::tuple_size_v<ChildsOfCalling> == 0)
    {
        return action(static_cast<Calling*>(from));
    }
    else {
        auto fromType = from->GetType();
        using Candidates = decltype(std::tuple_cat(std::declval<std::tuple<Calling>>(), std::declval<ChildsOfCalling>()));
        return DoComplexDispatchInternal<Candidates, Result>(
            from, std::forward<Action>(action), fromType
        );
    }
}

The only thing I don't like is that you have to define/register all child classes.

葬心 2024-08-31 02:19:59

在虚函数的情况下如何调用正确的函数?

Vtable将包含类的每个虚函数的条目,并且在运行时它将选择特定函数的地址并调用相应的函数。

在虚拟函数和函数模板的情况下,必须如何调用正确的函数?

在函数模板的情况下,用户可以使用任何类型调用此函数。这里相同的函数根据类型有多个版本。现在,在这种情况下,由于版本不同,对于相同的功能,必须维护 vtable 中的许多条目。

How right function is called in case of virtual?

Vtable will contain entries for each virtual function of class and at run time it will pick the address of specific function and it will call respective function.

How right function has to be called in case of virtual along with function template?

In case of function template, user can call this function with any type. Here same function has several versions based on type. Now, in this case for same function because of different versions, many entries in vtable has to be maintained.

仄言 2024-08-31 02:19:59

我查看了所有 14 个答案,有些给出了虚拟模板功能无法工作的原因,有些则给出了解决方法。一个答案甚至表明虚拟类可以具有虚拟函数。这应该不会太令人惊讶。

我的回答将给出标准不允许虚拟模板化函数的直接原因。既然这么多人抱怨。首先,我不敢相信有人评论说虚拟函数可以在编译时推导。这是我听过的最愚蠢的事情。

无论如何。我确信标准规定指向对象的 this 指针是其成员函数的第一个参数。

struct MyClass
{
 void myFunction();
}

// translate to
void myFunction(MyClass*);

现在我们已经清楚这一点了。然后我们需要知道模板的转换规则。模板化参数对其可以隐式转换的内容极为有限。我不记得全部内容,但您可以查看 C++ Primer 以获取完整参考。例如,T* 可转换为 const T*。数组可以转换为指针。但是,派生类不能作为模板化参数转换为基类。

struct A {};
struct B : A {};

template<class T>
void myFunction(T&);

template<>
void myFunction<A>(A&) {}

int main()
{
 A a;
 B b;

 myFunction(a); //compiles perfectly
 myFunction((A&)b); // compiles nicely
 myFunction(b); //compiler error, use of undefined template function
}

所以我希望你明白我的意思。您不能拥有虚拟模板函数,因为就编译器而言,它们是两个完全不同的函数;因为它们的隐含参数是不同类型的。

虚拟模板无法工作的另一个原因同样有效。因为虚表是快速实现虚函数的最佳方式。

I have looked at all the 14 answers, Some have reasons why virtual templates functions can't work, others show a work around. One answer even showed that virtual classes can have virtual functions. Which shouldn't be too surprising.

My answer will give a straight up reason why the standard doesn't allow virtual templated functions. Since so many have been complaining. Firstly though, I can't believe that some people have commented that virtual functions can be deduced at compile time. That is the dumbest thing I ever heard.

Anyhow. I am certain that the standard dictates that a this pointer to the object is the first argument to its member function.

struct MyClass
{
 void myFunction();
}

// translate to
void myFunction(MyClass*);

Now that we are clear on this. We then need to know the conversion rules for templates. A templated parameter is extremely limited to what it can implicitly convert to. I don't remember all of it, but you can check C++ Primer for complete reference. For example T* is convertible to const T*. Arrays are convertible to pointers. However, derived class is not convertible to base class as a templated parameter.

struct A {};
struct B : A {};

template<class T>
void myFunction(T&);

template<>
void myFunction<A>(A&) {}

int main()
{
 A a;
 B b;

 myFunction(a); //compiles perfectly
 myFunction((A&)b); // compiles nicely
 myFunction(b); //compiler error, use of undefined template function
}

So I hope you see where I am getting at. You cannot have a virtual template function because as far as the compiler is concerned they are two completedly different functions; as their implicit this parameter is of different type.

Another reasons why virtual templates can't work are equally valid. Since virtual tables are the best way to implement virtual functions fast.

习惯成性 2024-08-31 02:19:59

通读所有答案,有一件事很清楚 - 这要么“不可能”,要么需要一种巧妙的解决方法(可能是也可能不是可移植的)。因此,在走上给定的道路之前,您可能希望退后一步并重新评估您的需求。

根据代码的上下文,您可以采用多种完全不同的方法之一无需模板解决大致相同的问题。

如果您需要一个可以采用“通用”参数的虚函数,则模板的这些替代方法可能会起作用:

  • 多态性
  • 包装类由指向多种类型的指针组成,并且在运行时知道要使用哪种类型。
  • std::varianthttps://en.cppreference。 com/w/cpp/utility/variant
  • 足够勇敢/愚蠢吗? ...void *
  • 如果您能够评估编译时所需类型的上下文,那么 typedef 可能是一个非常干净的选项。

“带有 typedef 的虚拟”示例:

#ifdef SOME_DEFINE
typedef int MyCustomType;
#else
typedef std::string MyCustomType;
#endif // SOME_DEFINE

class Example 
{
public:
    virtual void doSomething( const MyCustomType &specs )=0;
};

Reading through all the answers, one thing is clear - this either "not possible" or it requires a clever workaround (which may or may not be portable). As such, you may wish to take a step back and reevaluate your needs before heading down a given path.

Based on the context of your code, you may be able to take one of many entirely different approaches to solve roughly the same problem without a template.

If you need a virtual function, which can take a "generic" argument, these alternate approaches to a template may work:

  • Polymorphism
  • Wrapper class composed of pointers to multiple types, and knows at runtime which it is to employ.
  • std::variant: https://en.cppreference.com/w/cpp/utility/variant
  • Brave/dumb enough? ...void *!
  • If you're in a position to evaluate the context of the type which is needed at compile time, a very clean option might be a typedef.

"virtual w/ typedef" Example:

#ifdef SOME_DEFINE
typedef int MyCustomType;
#else
typedef std::string MyCustomType;
#endif // SOME_DEFINE

class Example 
{
public:
    virtual void doSomething( const MyCustomType &specs )=0;
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文