【问题标题】:Managing a collection of vectors of templated derived types管理模板派生类型的向量集合
【发布时间】:2012-07-27 12:49:26
【问题描述】:

X:我想做的事:

我有以下类型:BaseTypeDerivedType<int k>(参见下面的代码),我需要处理派生类型 std::vector<DerivedType<k>>k = 1...KK 向量的集合。我想访问这些向量中的对象,并对它们执行依赖于k 的操作。 K 是一个编译时间常数。问题在实现中说明:

类型定义为:

#include <iostream>
#include <algorithm>

struct BaseType { // Interface of the DerivedTypes
  virtual void print(){std::cout << "BaseType!" << std::endl; }
};

template< int k >
struct DerivedType : public BaseType {
  static const int k_ = k;
  // ... function calls templated on k ...
  void print(){std::cout << "DerivedType: " << k_ << std::endl;}
};

template< int k >
void doSomething ( DerivedType<k>& object ) { object.print(); }

而我想做的是:

int main() {

  // My collection of vectors of the derived types:
  std::vector<DerivedType<0>> derType0(2);
  std::vector<DerivedType<1>> derType1(1);
  std::vector<DerivedType<2>> derType2(3);
  // ... should go to K: std::vector<DerivedType<K>> derTypeK;

  // Iterate over the derived objects applying a k-dependent templated function:
  std::for_each(begin(derType0),end(derType0),[](DerivedType<0>& object){
    doSomething<0>(object);
  });
  std::for_each(begin(derType1),end(derType1),[](DerivedType<1>& object){
    doSomething<1>(object);
  });
  std::for_each(begin(derType2),end(derType2),[](DerivedType<2>& object){
    doSomething<2>(object);
  });

  return 0;
}

我想避免重复代码,这样我只需要更改K,它是O(10) 的编译时间常数。理想情况下,我会有“更像”这样的东西:

// Pseudocode: do not try to compile this

create_derived_objects(DerivedType,K)
  = std::vector< std::vector<DerivedType<k>>* > my_K_derived_types;                                                  

for each vector<DerivedType<k>>* derivedTypes in my my_K_derived_types
  for each object in (*derivedTypes)
    doSomething<k> on object of type derivedType<k>
    // I could also restrict doSomething<k> to the base interface

派生类型的每个向量都包含O(10^6)O(10^9) 对象。最内层的循环是我的应用程序中最耗时的部分,因此使 dynamic_cast 仅作为最外层循环的一个选项。

Y:我尝试过的事情都没有成功。

我目前正在研究 Abrahams C++ Template Metaprogramming 这本书,看看我是否可以使用boost::mpl。我也在boost::fusion 上做教程,看看我是否也可以使用它。然而,这些库的学习曲线相当大,所以我想在我投入一周的时间之前先问一下,什么时候有更好、更简单的解决方案可用。

我的第一次尝试是包装我的向量std::vector&lt;DerivedType&lt;k&gt;&gt;,这样我就可以创建一个vector&lt;WrappedDerivedTypes*&gt;,并在for_each 循环中分别访问每个单个向量。但是,在循环中,我有一系列无法​​消除的if(dynamic_cast&lt;std::vector&lt;DerivedType&lt;0&gt;&gt;&gt;(WrappedVector) != 0 ){ do for_each loop for the derived objects } else if( dynamic_cast...) { do...} ...

【问题讨论】:

  • 你的doSomething 函数可以引用BaseType 吗?您展示的实现不依赖于k 模板参数。
  • 进一步了解 quamrana 的建议:为什么不直接将 doSomething() 设为虚拟方法,并为每个派生类型明确专门化它?然后,您可以将您的 k 个向量存储在指向BaseType 的向量的向量中。 (最初,您需要一个“工厂方法”来构造每个 DerivedType&lt;k&gt; 对象,但此后可以通过父 BaseType 类型统一处理它们。)就目前而言,我看不出通过制作 @987654348 可以获得什么@派生自BaseType

标签: c++ generics boost metaprogramming


【解决方案1】:

基于向量的通用链表、策略模式和通过链表递归应用策略的东西的递归解决方案怎么样? (注意:见最后改进版):

#include <iostream>
#include <vector>

template <int j>
class holder {
public:
    const static int k = j;
};

template <int j>
class strategy {
public:
    void operator()(holder<j> t)
    {
        std::cout << "Strategy " << t.k << std::endl;
    }
};

template <int k>
class lin_vector {
private:
    std::vector<holder<k>> vec;
    lin_vector<k-1> pred;
public:
    lin_vector(const lin_vector<k-1> &pred, std::vector<holder<k>> vec)
        : vec(vec), pred(pred) { }
    std::vector<holder<k>> get_vec() { return vec; }
    lin_vector<k-1> &get_pred() { return pred; }
};

template <>
class lin_vector<0> {
public:
    lin_vector() { }
};

template <int k, template <int> class strategy>
class apply_strategy {
public:
    void operator()(lin_vector<k> lin);
};

template <int k, template <int> class strategy>
void apply_strategy<k, strategy>::operator()(lin_vector<k> lin)
{
    apply_strategy<k-1, strategy>()(lin.get_pred());
    for (auto i : lin.get_vec())
    strategy<k>()(i);
}

template <template <int> class strategy>
class apply_strategy<0, strategy>
{
public:
    void operator()(lin_vector<0> lin) { /* does nothing */ } 
};


template <int k>
lin_vector<k> build_lin()
{
    return lin_vector<k>(build_lin<k-1>(), {holder<k>()});
}

template <>
lin_vector<0> build_lin()
{
    return lin_vector<0>();
}

int main(void)
{
    apply_strategy<5, strategy>()(build_lin<5>());
}

用 C++11 编译器编译它。 很可能您会发现构建lin_vector 需要大量复制这一事实并不令人满意,但您可以根据您的需要专门化结构(也许用指针替换pred 或将创建策略直接嵌入到链接中列表)。

编辑:这里有一个改进的版本,它避免了大量的复制并以更连贯和统一的方式处理列表构建和处理:

#include <iostream>
#include <vector>

template <int j>
class holder {
public:
    const static int k = j;
};

template <int k>
class lin_vector {
private:
    std::vector<holder<k>> vec;
    lin_vector<k-1> pred;
public:
    std::vector<holder<k>> &get_vec() { return vec; }
    lin_vector<k-1> &get_pred() { return pred; }
};

template <>
class lin_vector<0> {
public:
    lin_vector() { }
};

template <int k, template <int> class strategy>
class apply_strategy {
public:
    void operator()(lin_vector<k> &lin);
};

template <int k, template <int> class strategy>
void apply_strategy<k, strategy>::operator()(lin_vector<k> &lin)
{
    apply_strategy<k-1, strategy>()(lin.get_pred());
    strategy<k>()(lin.get_vec());
}

template <template <int> class strategy>
class apply_strategy<0, strategy>
{
public:
    void operator()(lin_vector<0> &lin) { /* does nothing */ } 
};

template <int j>
class strategy {
public:
    void operator()(std::vector<holder<j>> &t)
    {
        std::cout << "Strategy " << j << ", elements: ";
        for (auto v : t)
            std::cout << v.k << " ";
        std::cout << std::endl;
    }
};

template <int j>
class build_strategy {
public:
    void operator()(std::vector<holder<j>> &t)
    {
        for (unsigned int i = 0; i < j; i++)
            t.push_back(holder<j>());
    }
};

int main(void)
{
    const int K = 5;
    lin_vector<K> list;
    apply_strategy<K, build_strategy>()(list);
    apply_strategy<K, strategy>()(list);
}

【讨论】:

  • 这个基本设计可以稍微改进一下,尤其是关于lin_list 的东西。一个好主意是保留默认初始化的向量,然后使用apply_strategy 想法填充向量。通过这种方式,您将得到一个易于构建的通用结构(只需编写 lin_lis&lt;k&gt; l 就可以了),并且独立于用于填充向量的策略。
  • 哇!真的很好的答案!万分感谢 !为了避免复制向量,我将向量链表更改为向量指针链表。这样只需要复制向量指针。用指针替换 pred 是什么意思?据我了解, pred 包含前一个节点(及其对应的向量,或向量指针)。对吗?
  • 是的,没错。顺便说一句,我认为新版本更好:基本上 lin_list 是一个空向量的链表,然后通过定义策略来填充向量。完全不需要复制,您的构建策略被整齐地包含在内,您可以堆栈分配所有内容。
  • 我现在将研究用应用策略的想法填充向量。使用不同的填充策略会很棒。真的再次感谢!关于阅读内容以自己想出这样的解决方案的任何提示?我刚刚完成了设计模式,但我觉得在 c++ 中我最大的障碍是语言本身(与 python 不同)。
  • 我并不是真正的模板编程专家,但我觉得 Alexandrescu 的经典之作令人兴奋:amazon.com/Modern-Design-Generic-Programming-Patterns/dp/…
【解决方案2】:

一个没有虚拟调度的解决方案是可能的,尽管它可能是矫枉过正的。

您需要的第一件事是一个函数模板doSomething&lt;K&gt;(),您专门针对每个派生类型:

template <int K>
void doSomething(vector<DerivedType<K> >& x);

template <>
void doSomething<1>(vector<DerivedType<1> >& x) { ... }

template <>
void doSomething<2>(vector<DerivedType<2> >& x) { ... }   // etc.

然后您可以使用递归定义的struct 模板构建强类型向量集合:

template <int K>
struct vov {
    vov<K - 1> prev;
    vector<DerivedType<K> > v;
};

template <>
struct vov<1> {
    vector<DerivedType<1> > v;
};

最后,你可以写一个递归函数模板来处理这个结构:

template <int K>
void process(vov<K>& x) {
    doSomething(x.v);     // Type inference will find the right doSomething()
    process(x.prev);      // Here too
}

template <>
void process<1>(vov<1>& x) {
    doSomething(x.v);
}

现在主要代码如下所示:

vov<42> foo;
process(foo);

因为process()函数调用通过使用递归进行迭代,它可能会不必要地使用K个堆栈帧;但是它是尾递归,现代优化 C++ 编译器通常可以将其转换为纯迭代,而不会浪费堆栈。使用尾递归迫使我们以“反向”顺序处理向量,以便最后处理 DerivedType&lt;1&gt; 向量,但如有必要,可以使用 2 个 int 模板参数(一个将“向上计数”,而不是单个 int 参数向 1) “向下计数”。

请注意,在此解决方案中,从 BaseType 派生每个 DerivedType&lt;k&gt; 并没有带来任何好处——您不妨完全忘记 BaseType,除非您出于其他原因需要它。强>

很可能有 MPL 原语可以简化其中一些过程——如果有人知道它们,请随时编辑。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-25
    • 1970-01-01
    • 1970-01-01
    • 2018-03-30
    • 2011-11-19
    • 1970-01-01
    相关资源
    最近更新 更多