【问题标题】:Virtual template function in c++C++中的虚拟模板函数
【发布时间】:2018-02-01 14:45:17
【问题描述】:

我一直在寻找一种同时使用模板和多态的方法。这是我的问题的简化版本:

#include <iostream>
#include <vector>
using std::cout;
using std::endl;

//*******************************************************************
//*******************************************************************

struct DerivedStuff1
{
    static void eval() { cout << "evaluating DerivedStuff1" << endl; }
};

struct DerivedStuff2
{
    static void eval() { cout << "evaluating DerivedStuff2" << endl; }
};

//*******************************************************************
//*******************************************************************
class BaseClass
{
public:
    template<typename StuffType> virtual void eval() const = 0;
};

class DerivedClass1 : public BaseClass
{
public:
    template<typename StuffType> virtual void eval() const
    {
        std::cout << "We are in DerivedClass1: ";
        StuffType::eval();
    }
};

class DerivedClass2 : public BaseClass
{
public:
    template<typename StuffType> virtual void eval() const
    {
        std::cout << "We are in DerivedClass2: ";
        StuffType::eval();
    }
};

int main()
{
    BaseClass* c1 = new DerivedClass1;
    c1->eval<DerivedStuff1>();
    c1->eval<DerivedStuff2>();

    BaseClass* c2 = new DerivedClass2;
    c2->eval<DerivedStuff1>();
    c2->eval<DerivedStuff2>();

    return 0;
}

此代码无法编译,因为 C++ 中不允许使用虚拟模板函数。我找到了一些解决这个问题的方法(CRTP 等),但没有一个是真正令人满意的。有没有优雅的方法来解决这个问题?

【问题讨论】:

  • 可以制作类模板并在里面使用虚函数
  • @SemyonBurov - 但模板是特定于方法的;如何解决制作类模板的问题?
  • 为什么需要eval() virtual?
  • 您可以通过手动创建一个 vtable(使用 map&lt;string, function_ptr&gt;)并使用 typeid(T).name() 关闭正确的方法来完成某些操作。然而,这将是 slooooow。
  • eval() 的模板类型在某种程度上是相关的? DerivedStuff1DerivedStuff2 等有一个通用的基(虚拟?)类?

标签: c++ function templates polymorphism virtual


【解决方案1】:

您可以重新发明 vtable 并在运行时解析函数指针。但是,您必须在派生类上显式地实例化模板,但我没有看到任何不需要这样做的方法。

快速而肮脏的例子:

#include <map>
#include <iostream>

class Base {
public:
  typedef void (Base::*eval_ptr)();
  using eval_vtable = std::map<std::type_index, eval_ptr>;

  Base(eval_vtable const& eval_p) : eval_ptrs(eval_p) {}

  template<typename T>
  void eval() {
    auto handler = eval_ptrs.find(type_index(typeid(T)));
    if(handler != eval_ptrs.end()) {
      auto handler_ptr = handler->second;
      (this->*handler_ptr)();
    }
  }

  eval_vtable const& eval_ptrs;
};

class Derived : public Base {
public:
    Derived()
     : Base(eval_functions) {}

  template<typename T>
  void eval_impl() {
    std::cout << typeid(T).name() << "\n";
  }

  static eval_vtable eval_functions;
};

Base::eval_vtable Derived::eval_functions = {
  { type_index(typeid(int)), eval_ptr(&Derived::eval_impl<int>) },
  { type_index(typeid(float)), eval_ptr(&Derived::eval_impl<float>) },
  { type_index(typeid(short)), eval_ptr(&Derived::eval_impl<short>) },
};

int main(int argc, const char* argv[]) {
  Derived x;
  Base * x_as_base = &x;

  x_as_base->eval<int>(); // calls Derived::eval_impl<int>()
  return 0;
}

这不会完全快速,但它会给你我能想到的最接近模板化虚函数的东西。

编辑:为了记录,我不提倡任何人使用它。我宁愿重新审视设计,以避免首先在这个特定的角落被画。请将我的回答视为理论问题的学术解决方案,而不是实际的工程建议。

【讨论】:

  • 你应该使用 typeindex 而不是 typeid 指针; typeid 指针不能保证映射 1-1 和类型。
  • 我很高兴阅读您的回答,正如您所说,这太学术了!实际上工作且灵活但效率低下,我很怀念我还是一名博士生时被教授包围的工作浪费国家资金,这些工作有可能让他们写论文很快就被遗忘了!
  • 请注意,在我的具体情况下,速度并不是真正的问题,因为这个函数只会在这里和那里被调用几次。我更担心开发人员界面。不管隐藏的复杂性如何,我希望任何人都能够使用我的代码,而无需详细了解其内部工作。鉴于此,您是否仍然不推荐您的方法?
  • @Touloudou 当然我猜,如果它符合您的需求。一些宏甚至可以使这看起来不错。但由于您必须明确列出所有支持的类型,您不妨为每种类型使用单独的虚函数。
【解决方案2】:

visitor pattern 改变了运行时多态性并使运行时多态函数模板成为可能。除了模板化之外,它还有其他合法用途,所以我想你可以称之为优雅。

您的示例如下所示:

#include <iostream>

class DerivedStuff1 {
  public:
   static void eval() { std::cout << "Evaluating DerivedStuff1\n"; }
};

class DerivedStuff2 {
  public:
   static void eval() { std::cout << "Evaluating DerivedStuff2\n"; }
};

class DerivedClass1; class DerivedClass2;
class BaseClassVisitor {
  public:
    virtual void visit(DerivedClass1&) = 0;
    virtual void visit(DerivedClass2&) = 0;
};

class BaseClass {
  public:
    virtual void accept(BaseClassVisitor& v) = 0;
};

class DerivedClass1 : public BaseClass
{
  public:
    virtual void accept(BaseClassVisitor& v) { v.visit(*this); }
};

class DerivedClass2 : public BaseClass
{
  public:
    virtual void accept(BaseClassVisitor& v) { v.visit(*this); }
};


template <typename StuffType>
class EvalVisitor : public BaseClassVisitor
{
    virtual void visit(DerivedClass1&) {
        std::cout << "We are in DerivedClass1: ";
        StuffType::eval();
    }
    virtual void visit(DerivedClass2&) {
        std::cout << "We are in DerivedClass2: ";
        StuffType::eval();
    }

};

int main()
{
    EvalVisitor<DerivedStuff1> e1;
    EvalVisitor<DerivedStuff2> e2;

    BaseClass* c1 = new DerivedClass1;

    c1->accept(e1);
    c1->accept(e2);

    BaseClass* c2 = new DerivedClass2;

    c2->accept(e1);
    c2->accept(e2);

    return 0;
}

Demo

当然,Visitor 的所有缺点都适用于此。

【讨论】:

  • 我喜欢这种方法,但我只是有点担心开发者界面。照原样,如果有人想使用我的代码,他需要创建一个访问者并将其传递给接受函数。有没有办法进一步封装这一点,以便访问者对用户来说甚至不明显?基本上,添加一个模板函数 eval() 来创建访问者并将其传递给接受函数?
  • 此外,现在已经将实现从 DerivedClasses 移至 EvalVisitor。这对我来说是个问题,因为这些派生类包含它们在 eval 函数中使用的大量数据。
  • 如果你创建了 EvalVisitor 类,你当然可以在一些用户友好的函数模板中对用户隐藏它。如果您希望用户添加新功能,他们需要了解访问者基础设施。
  • 您还可以在 DerivedClassN 的(非虚拟)成员函数 trmplates 中拥有实际实现,访问者只需调用它们即可。它知道要传递的特定派生类和特定模板参数,所以这不是问题。例如。 virtual void visit(DerivedClass1&amp; d) { d.eval&lt;StuffYype&gt;(); }
【解决方案3】:

您不能混合使用模板(编译时)和多态(运行时)。就是这样。

因此,一个可行的解决方法是删除模板。例如,它可以采用函数指针或更多的多态性:

//*******************************************************************
//*******************************************************************

struct InterfaceStuff{
  virtual void eval() = 0;
}

struct DerivedStuff1 : public InterfaceStuff
{
    void eval() { cout << "evaluating DerivedStuff1" << endl; }
};

struct DerivedStuff2 : public InterfaceStuff
{
    void eval() { cout << "evaluating DerivedStuff2" << endl; }
};

//*******************************************************************
//*******************************************************************
class BaseClass
{
public:
    virtual void eval(InterfaceStuff* interface) const = 0;
};

class DerivedClass1 : public BaseClass
{
public:
    virtual void eval(InterfaceStuff* interface) const
    {
        std::cout << "We are in DerivedClass1: ";
        interface->eval();
    }
};

class DerivedClass2 : public BaseClass
{
public:
    virtual void eval(InterfaceStuff* interface) const
    {
        std::cout << "We are in DerivedClass2: ";
        interface->eval();
    }
};

另一个可能的解决方法是删除多态性,只需使用更多模板:

struct DerivedStuff1
{
    static void eval() { cout << "evaluating DerivedStuff1" << endl; }
};

struct DerivedStuff2
{
    static void eval() { cout << "evaluating DerivedStuff2" << endl; }
};

//*******************************************************************
//*******************************************************************
class BaseClass
{
public:
    template<typename Eval,typename StuffType> void eval() const
    {
        Eval::eval();
        StuffType::eval();
    }
};

class DerivedClass1 : public BaseClass
{
};

class DerivedClass2 : public BaseClass
{
};

一种方式,另一种方式,你必须选择一种方式。

【讨论】:

  • 我可以通过多种方式编写运行时多态模板代码; tyoe 擦除,各种双调度(或多调度)策略,通过 CRTP 快速投射等。当你说不能做的时候,请更具体; OP 的做法不起作用,如果您的目标是避免在可执行文件中重新实现 c++ 编译器,通常需要一些额外的限制,但“不能”有点强。通常是“不应该”。
【解决方案4】:

由于 C++ 中不允许使用虚拟模板方法,因此您可以制作类模板并调用类模板参数的静态函数。

#include <iostream>
#include <vector>
using std::cout;
using std::endl;

//*******************************************************************
//*******************************************************************

struct DerivedStuff1
{
    static void eval() { cout << "evaluating DerivedStuff1" << endl; }
};

struct DerivedStuff2
{
    static void eval() { cout << "evaluating DerivedStuff2" << endl; }
};

//*******************************************************************
//*******************************************************************
class BaseClass
{
public:
    virtual void eval() const = 0;
};

template<typename StuffType>
class DerivedClass1 : public BaseClass
{
public:
    virtual void eval() const
    {
        std::cout << "We are in DerivedClass1: ";
        StuffType::eval();
    }
};

template<typename StuffType>
class DerivedClass2 : public BaseClass
{
public:
    virtual void eval() const
    {
        std::cout << "We are in DerivedClass2: ";
        StuffType::eval();
    }
};

int main()
{
    BaseClass* c1 = new DerivedClass1<DerivedStuff1>;
    c1->eval();
    c1 = new DerivedClass1<DerivedStuff2>;
    c1->eval();

    BaseClass* c2 = new DerivedClass2<DerivedStuff1>;
    c2->eval();
    c2 = new DerivedClass2<DerivedStuff2>;
    c2->eval();

    // deletes

    return 0;
}

输出

We are in DerivedClass1: evaluating DerivedStuff1
We are in DerivedClass1: evaluating DerivedStuff2
We are in DerivedClass2: evaluating DerivedStuff1
We are in DerivedClass2: evaluating DerivedStuff2

【讨论】:

    猜你喜欢
    • 2017-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-28
    • 2011-12-19
    • 2021-11-08
    • 1970-01-01
    相关资源
    最近更新 更多