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

时间:2010-03-01 06:26:35

标签: c++ templates virtual-functions c++-faq

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

如果它们可以是虚拟的,那么可以使用这样一个函数的场景示例是什么?

13 个答案:

答案 0 :(得分:295)

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

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

但是,有一些强大而有趣的技术源于结合多态性和模板,特别是所谓的 type erasure

答案 1 :(得分:108)

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

  

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

答案 2 :(得分:31)

C ++目前不允许使用虚拟模板成员函数。最可能的原因是实施它的复杂性。拉金德拉很好地说明了为什么现在无法做到这一点,但可以通过合理的标准变更来实现。特别是如果你考虑虚函数调用的位置,那么实际存在模板函数的实例数并且构建vtable似乎很困难。标准人员现在还有许多其他事情需要做,C ++ 1x也是编译器编写者的大量工作。

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

答案 3 :(得分:16)

虚拟功能表

让我们从虚拟功能表的一些背景知识及其工作方式开始(source):

  

[20.3]虚拟和非虚拟之间的区别是什么   成员函数被调用?

     

静态解析非虚拟成员函数。那就是   成员函数是基于静态选择的(在编译时)   对象的指针(或引用)的类型。

     

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

     

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

     

在发送虚拟功能期间,运行时系统紧随其后   对象的v指针指向类的v表,然后是   v表中适当的插槽到方法代码。

     

上述技术的空间成本开销是名义上的:额外的   每个对象的指针(但仅适用于需要动态的对象)   绑定),每个方法加一个额外的指针(但仅限于虚拟   方法)。时间成本开销也相当标准:与a相比   正常函数调用,虚函数调用需要两个额外的   提取(一个获取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)在基类

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未在基类中声明。


这是另一个堆栈溢出答案,解决方法: need a virtual template member workaround

答案 4 :(得分:12)

在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条目。

答案 5 :(得分:11)

不,他们不能。但是:

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);
};
如果您只想拥有一个通用接口并将实现推迟到子类,那么

具有相同的效果。

答案 6 :(得分:5)

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

答案 7 :(得分:3)

回答问题的第二部分:

  

如果它们可以是虚拟的,那么可以使用这样一个函数的场景示例是什么?

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

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

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

答案 8 :(得分:2)

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

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

在那里,'虚拟'模板方法(Base::Method)调用相应的虚拟方法(Base::VMethod之一),而后者又调用模板方法实现(Impl::TMethod)。

只需要在派生实现(TMethodAImpl)中实现模板方法BImpl并使用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

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

答案 9 :(得分:2)

尽管许多人都回答了一个较老的问题,但我认为一种简洁的方法(与发布的其他方法没有什么不同)是使用次要宏来帮助简化类声明的重复。

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

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

答案 10 :(得分:0)

至少使用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

答案 11 :(得分: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

答案 12 :(得分:0)

我当前的解决方案如下(禁用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
        );
    }
}

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