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

如果它们可以是虚拟的,那么有什么场景可以使用这样的函数呢?


当前回答

回答问题的第二部分:

如果它们可以是虚拟的,那么有什么场景可以使用这样的函数呢?

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

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

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

其他回答

不,他们不能。但是:

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

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

下面的代码可以在windows 7上使用mingwg++ 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&)'

所以很明显:

虚成员函数可以在类模板中使用。编译器可以很容易地构造虚表 将类模板成员函数定义为虚函数是不可能的,如你所见,很难确定函数签名和分配虚表项。

回答问题的第二部分:

如果它们可以是虚拟的,那么有什么场景可以使用这样的函数呢?

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

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

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

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

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

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

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

如果预先知道模板方法的类型集,则'虚拟模板方法'有一个变通方法。

为了说明这个想法,在下面的例子中只使用了两种类型(int和double)。

在那里,一个“虚拟”模板方法(Base:: method)调用相应的虚拟方法(Base:: VMethod之一),后者反过来调用模板方法实现(Impl::TMethod)。

只需要在派生实现(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可以被设为public并直接使用)。 我添加它,使它看起来像一个实际的“虚拟”模板方法。