通用设计混合了好奇的循环模板模式。 C ++ [英] Generic design mixed with curiously recurring template pattern. C++

查看:131
本文介绍了通用设计混合了好奇的循环模板模式。 C ++的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑这种问题。我有一个 Base 类和三个派生自 Base 的类。例如: DerivedA DerivedB DerivedC 。每个派生类都有其唯一的容器。因此 DerivedA 具有 std :: vector< int> DerivedB std :: set< int> DerivedC std :: map< int ,std :: string> 。而我想要一个 Base 中的接口来访问它当前指向的派生类的容器。

  Base * d1 = new DerivedA; 
for(std :: vector< int> :: iterator iter = d1-> begin(); iter!= d1-> end(); ++ iter)
{
//处理
}

我试图将每个容器包装到不同的类并保持一个指针他们的基地
基础类中。

 类科亚; 

模板< class T>
class traits;

template<>
class traits< CollA>
{
public:
typedef vector< int>容器;
};


模板< class T>
class Coll
{
public:
typedef typename traits< T> :: container container;
typename container :: iterator begin()const
{
}
};


class CollA:public Coll< CollA>
{
typedef traits< CollA> :: container container;
public:
container :: iterator begin()
{
return V.begin();
}
private:
vector< int> V;
};

class Base
{
public:
Base()
{
}
//在这里做什么?我必须指向Coll;但是Coll本身就是一个模板
};

向我推荐一些东西。

解决方案

为了做你想要的,你需要定义一个常见的类型的迭代器,可以从派生类中的不同的 begin() end()返回。 p>

在这之前,当然,你需要确定你希望迭代器做什么,就像Yakk在他的评论中解释的那样。对于初学者,您需要确定通过这种迭代器间接产生的 value_type 。给定三个不同容器的唯一常见类型是 const int ,作为 std :: map s是 const std :: set 迭代器是 const iterators(因为元素是键本身)。因此,当使用常用的迭代器类型进行迭代时,您只能在其中观察到 int s。



现在,迭代器实现将需要根据派生类派生类来调用不同的代码(在运行时)。这是类型擦除的典型用例。正确完成后,只要支持所需的界面,这将允许您包装任何类型的迭代器。然而,在你的情况下,你可能不需要去那么远,因为我想你知道你需要支持的全套容器,所以迭代器类型是众所周知的,也是有限的。



这意味着您可以使用 boost :: variant 来存储包装的迭代器。这应该比完全类型的擦除解决方案更有效,因为它避免了一些内部虚拟函数调用和可能的一些堆分配(除非类型擦除解决方案可以使用某种类型的小对象优化,这对于迭代器是相当可能的,但是甚至是更复杂的实现)。



这是一个这样的迭代器的框架实现,以及使用它的类层次结构和一些简单的测试代码。请注意,我只实现了使循环工作所需的基本迭代器功能。

  #include< iostream> 
#include< string>
#include< vector>
#include< set>
#include< map>
#include< iterator>
#includeboost / variant.hpp


//帮助函数对象类型来实现变量迭代器上的每个运算符。

struct indirection_visitor:boost :: static_visitor< const int&>
{
const int& operator()(std :: vector< int> :: iterator i)const {return * i; }
const int& operator()(std :: set< int> :: iterator i)const {return * i; }
const int& operator()(std :: map< int,std :: string> :: iterator i)const {return i-> first; }
};

struct prefix_increment_visitor:boost :: static_visitor<>
{
template< typename I> void operator()(I& i)const {++ i; }
};


//迭代器本身。
//它应该可以隐藏内部变体,在这种情况下,非成员操作符
//应该被声明为朋友。

struct var_iterator:std :: iterator< std :: bidirectional_iterator_tag,const int>
{
var_iterator(){}
template< typename I> var_iterator(I i):it(i){}

boost :: variant< std :: vector< int> :: iterator,std :: set< int> :: iterator,std :: map< int,std :: string> :: iterator>它;

const int& operator *(){return boost :: apply_visitor(indirection_visitor(),it); }

var_iterator& operator ++()
{
boost :: apply_visitor(prefix_increment_visitor(),it);
return * this;
}
};

inline bool operator ==(var_iterator i1,var_iterator i2){return i1.it == i2.it; }
inline bool operator!=(var_iterator i1,var_iterator i2){return!(i1 == i2); }


//这是类层次结构。
//我们仅使用CRTP来避免复制和粘贴每个派生类的begin()和end()覆盖。

struct Base
{
virtual var_iterator begin()= 0;
virtual var_iterator end()= 0;
};

模板< typename D> struct Base_container:Base
{
var_iterator begin()override {return static_cast< D *>(this) - > container.begin(); }
var_iterator end()override {return static_cast< D *>(this) - > container.end(); }
};

struct DerivedA:Base_container< DerivedA>
{
std :: vector< int>容器;
};

struct DerivedB:Base_container< DerivedB>
{
std :: set< int>容器;
};

struct DerivedC:Base_container< DerivedC>
{
std :: map< int,std :: string>容器;
};


//快速测试。

void f(Base * bp)
{
for(auto iter = bp-> begin(); iter!= bp-> end(); ++ iter)
{
std :: cout<<< * iter<<
}
std :: cout<<< \\\
;

//我们有足够的工作范围为基础。
for(auto i:* bp)
std :: cout<<< i<
std :: cout<<< \\\
;
}

int main()
{
DerivedA da;
da.container = {1,2,3}
f(& da);
DerivedB db;
db.container = {4,5,6};
f(& db);
DerivedC dc;
dc.container = std :: map< int,std :: string> {{7,seven},{8,8},{9,9}};
f(& dc);
}

实施说明:




  • 如上所述,这不是一个完整的双向迭代器;我选择了该标签作为容器类型中最强大的常见迭代器。

  • 我编译和(表面上)测试了Clang 3.6.0中的代码和C ++ 11中的GCC 5.1.0模式,在Visual C ++ 2013中,使用boost 1.58.0。

  • 该代码在上面的编译器(以及Visual C ++ 2015 CTP6)中也适用于C ++ 14模式,但是需要一个小小的改变,因为1.58的提升(我必须报告),否则你会得到歧义的错误。您需要删除 indirection_visitor 的基类,然后自动确定此访问者的返回类型。这仅适用于C ++ 14,因为它在内部使用 decltype(auto),这是导致歧义的新代码。 boost的早期版本没有这个问题,但是没有自动检测返回类型。

  • 在C ++ 14模式和boost 1.58中,可以使用通用的lambdas来实现简单的访问者,如 prefix_increment_visitor ,这使代码更直接。

  • 我从第一版代码中删除了比较访问者,如 boost :: variant 已经提供了一个默认的相等运算符,这对于这种情况是足够的(这个例子足够长了)

  • 您可以在必需的位置添加 const ,以获得真正的const迭代器行为(qualify begin() end(),在CRTP中使用 static_cast< const D *> ,声明变体包含 />
  • 当然,你可以实现某种穷人的变种,避免使用boost,而 boost :: variant 使一切更容易,清晰r并且更安全。


Consider this kind of problem. I have a Base class and three classes derived from Base. For instance: DerivedA, DerivedB and DerivedC. Each derived class has its unique container. Hence DerivedA has std::vector<int>, DerivedB has std::set<int> and DerivedC has std::map<int, std::string>. And I want an interface in Base to access the container of derived class on which it is currently pointed to.

Base* d1 = new DerivedA;
for(std::vector<int>::iterator iter = d1->begin(); iter != d1->end(); ++iter)
{
     //processing
}

I tried to wrap each container to separate class and keep a pointer of their base in the Base class.

class CollA;

template<class T>
class traits;

template<>
class traits<CollA>
{
  public:
  typedef vector<int> container; 
};


template<class T>
class Coll
{
  public:
    typedef typename traits<T>::container container;
    typename container::iterator begin() const
    {
    }
};


class CollA : public Coll<CollA>
{
  typedef traits<CollA>::container container;
  public:
    container::iterator begin()
    {
      return V.begin();
    }
    private:
    vector<int> V;
};

class Base
{
  public:
    Base()
    {
    }
    // what to do here? I must keep a pointer to Coll; But Coll itself is a template
};

Suggest me something. I am kind of lost in this horrible design.

解决方案

In order to do what you want, you need to define a common type of iterator that can be returned from the different begin() and end() overrides in the derived classes.

Before that, of course, you need to decide what exactly you want that iterator to do, as Yakk explained in his comment. For starters, you need to decide what value_type will result from indirecting through such an iterator. The only common type that I can think of given your three different containers is const int, as keys in std::maps are const and std::set iterators are const iterators (since the elements are keys themselves). So, when iterating using the common iterator type, you'll only be able to observe the ints in there.

Now, the iterator implementation will need to call different code (at runtime) depending on the derived class from which it originated. This is a typical use case for type erasure. When done properly, this would allow you to wrap any kind of iterator, as long as it supports the interface you need. In your case however, you may not need to go that far, since I suppose you know the full set of containers you need to support, so the set of iterator types is well known and bounded as well.

This means you can use a boost::variant to store the wrapped iterator. This should be more efficient than a full type erasure solution, since it avoids some internal virtual function calls and possibly some heap allocations (unless the type erasure solution can use some kind of small object optimization, which is fairly possible for iterators, but is even more complicated to implement).

Here's a skeleton implementation of such an iterator, together with the class hierarchy using it and some simple test code. Note that I've only implemented the basic iterator functionality that's needed to make your loop work.

#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <iterator>
#include "boost/variant.hpp"


//Helper function object types to implement each operator on the variant iterator.

struct indirection_visitor : boost::static_visitor<const int&>
{
   const int& operator()(std::vector<int>::iterator i) const { return *i; }
   const int& operator()(std::set<int>::iterator i) const { return *i; }
   const int& operator()(std::map<int, std::string>::iterator i) const { return i->first; }
};

struct prefix_increment_visitor : boost::static_visitor<>
{
   template<typename I> void operator()(I& i) const { ++i; }
};


//The iterator itself.
//It should probably hide the internal variant, in which case the non-member operators 
//should be declared as friends.

struct var_iterator : std::iterator<std::bidirectional_iterator_tag, const int>
{
   var_iterator() { }
   template<typename I> var_iterator(I i) : it(i) { }

   boost::variant<std::vector<int>::iterator, std::set<int>::iterator, std::map<int, std::string>::iterator> it;

   const int& operator*() { return boost::apply_visitor(indirection_visitor(), it); }

   var_iterator& operator++()
   {
      boost::apply_visitor(prefix_increment_visitor(), it);
      return *this;
   }
};

inline bool operator==(var_iterator i1, var_iterator i2) { return i1.it == i2.it; }
inline bool operator!=(var_iterator i1, var_iterator i2) { return !(i1 == i2); }


//Here's the class hierarchy.
//We use CRTP only to avoid copying and pasting the begin() and end() overrides for each derived class.

struct Base
{
   virtual var_iterator begin() = 0;
   virtual var_iterator end() = 0;
};

template<typename D> struct Base_container : Base
{
   var_iterator begin() override { return static_cast<D*>(this)->container.begin(); }
   var_iterator end() override { return static_cast<D*>(this)->container.end(); }
};

struct DerivedA : Base_container<DerivedA>
{
   std::vector<int> container;
};

struct DerivedB : Base_container<DerivedB>
{
   std::set<int> container;
};

struct DerivedC : Base_container<DerivedC>
{
   std::map<int, std::string> container;
};


//Quick test.

void f(Base* bp)
{
   for(auto iter = bp->begin(); iter != bp->end(); ++iter)
   {
      std::cout << *iter << ' ';
   }
   std::cout << '\n';

   //We have enough to make range-based for work too.
   for(auto i : *bp)
      std::cout << i << ' ';
   std::cout << '\n';
}

int main()
{
   DerivedA da;
   da.container = {1, 2, 3};
   f(&da);
   DerivedB db;
   db.container = {4, 5, 6};
   f(&db);
   DerivedC dc;
   dc.container = std::map<int, std::string>{{7, "seven"}, {8, "eight"}, {9, "nine"}};
   f(&dc);
}

Implementation notes:

  • As mentioned above, this is not a complete bidirectional iterator; I chose that tag as the most powerful common iterator among your container types.
  • I compiled and (superficially) tested the code in Clang 3.6.0 and GCC 5.1.0 in C++11 mode, and in Visual C++ 2013, using boost 1.58.0.
  • The code works in C++14 mode as well in the compilers above (and also in Visual C++ 2015 CTP6), but needs a small change because of a bug in boost 1.58 (I'll have to report that), otherwise you'll get an ambiguity error. You need to remove the base class of indirection_visitor and let the return type of this visitor be determined automatically. This only works in C++14, as it uses decltype(auto) internally, and it's this new code that causes the ambiguity. Earlier versions of boost don't have this problem, but don't have autodetection of return types either.
  • In C++14 mode and boost 1.58, you can use generic lambdas to implement simple visitors like prefix_increment_visitor, which makes the code more straightforward.
  • I removed the comparison visitors from my first version of the code, as boost::variant already provides a default equality operator and it's enough for this case (the example is long enough as it is).
  • You can add const in the required places to get true const iterator behaviour if needed (qualify begin() and end(), use static_cast<const D*> in CRTP, declare the variant to contain const_iterators, adjust the visitor).
  • You can, of course, implement some sort of poor-man's variant and avoid using boost, but boost::variant makes everything much easier, cleaner and safer.

这篇关于通用设计混合了好奇的循环模板模式。 C ++的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆