【问题标题】:Imitating dynamic polymorphism with CRTP + variadic templates用 CRTP + 可变参数模板模拟动态多态性
【发布时间】:2016-09-25 19:18:28
【问题描述】:

我需要用相当大的类替换代码中的动态多态性,但我遇到了 CRTP + 可变参数模板设计的问题。

为了说明,我在下面提供了一个玩具问题。

有一个动态多态的类,使用如下接口:

int main() { 
  Loop loop;
  Add add ;
  loop.set(add);
  loop.run();
  Sub sub ;
  loop.set(sub); 
}

当然,SubAdd 派生自基类 Operationop(double & v) 是一个虚方法。

由于其他原因我需要保留这个接口,同时通过静态多态来改变动态,这是我到目前为止的解决方案:

template <typename T> class Operation { 
public: 
  double d=1.;
  void op(double & d) { static_cast<T*>(this)->op(d);}
};
class Add final: public Operation<Add> { 
public: void op(double & v) { v += d; }
};
class Sub final: public Operation<Sub> {
public: void op(double & v) { v -= d; }
};

对于Loop 类:

template <typename... Types>
class MetaLoop {
public:
  double d = 0 ;
  template <typename T> void set(T & t) { 
  std_cxx17::get<std::vector<T*> >(data).push_back(&t) ; 
  }
  template <typename T> void run() { 
    for (auto j = 0; j<2E9 ; ++j)
      std_cxx17::get<std::vector<T*> >(data).back()->op(d);
    std::cout << d << std::endl; 
  }
private:
  std::tuple<std::vector<Types*>... > data ;
};
typedef MetaLoop<Add,Sub> Loop ;

std_cxx17::get&lt;T*&gt;(data) 是对按类型选择元组中元素的方法的调用,类似于this post

然而,这个解决方案几乎需要我想要的接口,除了需要向run() 提供模板参数,即run&lt;Add&gt;run&lt;Sub&gt; 以及需要注册 AddSubMetaLooptypedef MetaLoop&lt;Add,Sub&gt; Loop

我想知道是否可以在不使用宏的情况下:

  1. 自动将Operation&lt;T&gt; 类注册到MetaLoop(即自动调用typedef Metaloop&lt;...operations...&gt;
  2. 避免将模板类提供给run 方法。 (即致电run() 而不是run&lt;Add&gt;()
  3. 从库中获取此方法:是否有库(例如 Boost)可以静态提供这种多态性?

请记住,我知道我可以使用其他几种架构,但我必须使用许多其他限制。这只是一个用来举例说明的玩具问题。

提前致谢!

编辑 1:

问题 2 已回答!见这里:http://coliru.stacked-crooked.com/a/e68ea7faabba3e40

https://www.reddit.com/user/17b29a致敬

【问题讨论】:

  • 如果您不明确指定Add,您希望它运行什么?是否应该同时运行AddSub
  • 我希望它运行已设置的最后一个类型。谢谢!
  • 那么,您希望每次只有一个活动类型吗?为什么要将所有内容都存储在向量中?
  • 好问题!谢谢回答!我将所有内容存储在一个向量中,因为我希望存储多个 Add 的实例。
  • 好的,但是如果您关心的是永远只使用最后一个,为什么还要跟踪其他的呢?你不能简单地替换活动的吗? (我要求能够给你一个正确的答案)。

标签: c++ boost polymorphism tuples crtp


【解决方案1】:

您可以类型擦除您的类型,如下例所示:

#include <iostream>
#include <vector>
#include <utility>

template<typename T>
struct Operation {
    void op(double &d) { static_cast<T*>(this)->op(d) ; }
    double d{1};
};

struct Add: Operation<Add> {
    void op(double &v) { v += d; }
};

struct Sub: Operation<Sub> {
    void op(double &v) { v -= d; }
};

class Loop {
    using Fn = void(*)(double &, void *);

    template<typename T>
    static void op(double &d, void *op) {
        static_cast<T*>(op)->op(d);
    }

public:
    template<typename T>
    void set(T &t) {
        data.emplace_back(&op<T>, &t);
    }

    void run() {
         auto &p = data.back();
         for(auto j = 0; j < 10; ++j) {
             p.first(d, p.second);
         }

         std::cout << d << std::endl;
    }

private:
    std::vector<std::pair<Fn, void*>> data;
    double d = 0;
};

int main() {
    Loop loop;
    Add add;
    loop.set(add);
    loop.run();
    Sub sub;
    loop.set(sub);
    // ...
}

另请注意,在这种情况下,CRTP 习惯用法是无用的,您可以删除模板类 Operation
简单定义AddSub如下:

struct Add {
    void op(double &v) { v += d; }
    double d{1};
};

struct Sub {
    void op(double &v) { v -= d; }
    double d{1};
};

如果您想区分 AddSub 并提供不同的算法,您可以使用特化。
举个例子:

class Loop {
    using Fn = void(*)(double &, void *);

    template<typename T>
    static void op(double &d, void *op);

public:
    template<typename T>
    void set(T &t) {
        data.emplace_back(&op<T>, &t);
    }

    void run() {
        auto &p = data.back();
        for(auto j = 0; j < 10; ++j) {
            p.first(d, p.second);
        }

        std::cout << d << std::endl;
    }

private:
    std::vector<std::pair<Fn, void*>> data;
    double d = 0;
};

template<>
void Loop::op<Add>(double &d, void *op) {
    Add *add = static_cast<Add*>(op);
    // do whatever you want with add
    add->op(d);
}

template<>
void Loop::op<Sub>(double &d, void *op) {
    Sub *sub = static_cast<Sub*>(op);
    // do whatever you want with sub
    sub->op(d);
}

请注意,该解决方案符合问题中列出的所有要求,并且适用于 OP 作为示例提供的 main

【讨论】:

  • 谢谢@skypjack!不幸的是,我的对象 AddSub 比我的示例中显示的要复杂得多,而且我在 Loop 中对它们所做的操作也比显示的要复杂得多。因此,我真的需要访问指向它们的指针。我得到了修复界面的答案,我认为它很整洁!我现在只需要一种方法来实例化所有Operation&lt;T&gt; 专业化,一切都会得到解决! :-)
  • @Astor 在我的示例中,您实际上是 访问 指向它们的指针。参见Loopop 方法的实现,它将对象转换回它们的原始类型。
  • @Astor 为答案添加了更多详细信息。让我知道是否清楚。如果你愿意,你仍然可以使用 CRTP,即使在这种情况下并不严格要求。
  • 你可以,但我必须存储尽可能多的 Fn 函数作为我的 Operation&lt;T&gt; 的成员函数,但最重要的不是那个,而是我必须保持静态的事实代码,在您的示例中(就我的编译器而言),每次调用 p.first(d, p.second) 时都会取消引用一个指针,就像在动态多态代码中一样。当迭代 2E9 次时,它的执行速度要慢 3 倍。非常感谢,老实说!我应该更清楚我的规格。目前我只需要找到一种方法将所有 Operation&lt;T&gt; 注册到 MetaLoop...
  • @Astor 您可以将循环移动专用函数中并在启动之前获取参考。确实不是什么大问题。无论如何,这是你的代码,随你喜欢。祝你好运。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-26
  • 2010-09-20
  • 1970-01-01
相关资源
最近更新 更多