【问题标题】:Templates and STL模板和 STL
【发布时间】:2011-02-10 20:50:10
【问题描述】:

以下代码表示基于std::vector的容器

template <typename Item>
struct TList
{
    typedef std::vector <Item> Type;
};


template <typename Item>
class List
{
private
            typename TList <Item>::Type items;
    ....
}

int main()
{
  List <Object> list;
}

是否可以将 std::vector 模板化并创建一个通用容器,类似的东西?

template <typename Item, typename stl_container>
struct TList
{
    typedef stl_container<Item>;
};

其中 stl_container 代表 std::vector、std::list、std::set...?我想在创建时选择容器的类型。

List <Object, std::vector> list; //vector of objects, not a real code
List <Object, std::vector> list; //list of objects, not a real code

感谢您的回答...

更新问题:

我尝试了以下代码但有错误:

#include <vector>
template <typename Item, typename Container>
struct TList
{
   typedef typename Container <Item>::type type; //Error C2059: syntax error : '<', Error C2238: unexpected token(s) preceding ';
};


template <typename T>
struct vector_container
{
  typedef std::vector<T> type;
};

int _tmain(int argc, _TCHAR* argv[])
{
TList <int, vector_container> v;
TList <int, map_container> m;
}

【问题讨论】:

  • 谁会使用这个类?为什么List 不能只使用typedef vector/list/set&lt;Item&gt; items?在给定容器类型和值类型的情况下,将这两者简单地放在一起的类的目的是什么?
  • Re: 编辑,还是不明白为什么不能写List&lt;std::vector&lt;Object&gt; &gt; list;

标签: c++ templates stl


【解决方案1】:

是的,但不是直接的:

template <typename Item, template <typename> class Container>
struct TList
{
    typedef typename Container<Item>::type type;
};

然后你可以定义不同的容器策略:

template <typename T>
struct vector_container
{
    typedef std::vector<T> type;
};

template <typename T>
struct map_container
{
    typedef std::map<T, std::string> type;
};

TList<int, vector_container> v;
TList<int, map_container> m;

不过有点冗长。* 要直接执行操作,您需要使用 the route described by James,但正如他指出的那样,这最终非常不灵活。

但是,使用 C++0x 我们可以做到这一点:

#include <map>
#include <vector>

template <typename Item,
            template <typename...> class Container, typename... Args> 
struct TList
{
    // Args lets the user specify additional explicit template arguments
    Container<Item, Args...> storage;
};

int main()
{
    TList<int, std::vector> v;
    TList<int, std::map, float> m;
}

完美。不幸的是,没有办法在 C++03 中重现这一点,除非通过上述介绍的间接策略类。


*我想强调的是,“有点冗长”是指“这是非正统的”。您的问题的正确解决方案是标准库所做的,as Jerry explains。您只需让容器适配器的用户直接指定整个容器类型:

template <typename Item, typename Container = std::vector<Item>>
struct TList
{};

但这留下了一个大问题:如果我不希望容器的值类型是Item 而是something_else&lt;Item&gt;,该怎么办?换句话说,如何将现有容器的值类型更改为其他类型?在你的情况下你没有,所以不要再读了,但在我们这样做的情况下,我们想要重新绑定一个容器。

不幸的是,容器没有这个功能,虽然分配器有:

template <typename T>
struct allocator
{
    template <typename U>
    struct rebind
    {
        typedef allocator<U> type;
    };

    // ...
};

这允许我们在给定allocator&lt;T&gt; 的情况下获得allocator&lt;U&gt;。如果没有这种侵入性实用程序,我们如何对容器做同样的事情?在 C++0x 中,这很容易:

template <typename T, typename Container>
struct rebind; // not defined

template <typename T, typename Container, typename... Args>
struct rebind<T, Container<Args...>>
{
    // assumes the rest are filled with defaults**
    typedef Container<T> type; 
};

给定std::vector&lt;int&gt;,例如,我们可以执行rebind&lt;float, std::vector&lt;int&gt;&gt;::type。与之前的 C++0x 解决方案不同,这个解决方案可以在 C++03 中通过宏和迭代来模拟..


**注意这种机制可以变得更强大,比如指定保留哪些参数,重新绑定哪些参数,在用作参数之前重新绑定哪些参数等,但这留给读者练习。 :)

【讨论】:

  • 好主意。是否有任何问题无法通过额外的间接层解决?
  • @James:不。如果我用镜子,我什至可以看到我的额头。
  • 大声笑——这很好。 cmets很搞笑。 +1。
  • 为行typedef typename Container ::Type type;编译器说:错误C2059:语法错误:'
  • 使用模板别名可以写template&lt;typename T, typename Container&gt; using rebind = typename detail::rebind&lt;T, Container&gt;::type;
【解决方案2】:

我有点困惑,为什么一些非常聪明(而且能干)的人会说不。

除非我误读了您的问题,否则您要完成的工作实际上与标准库中的“容器适配器”相同。每个都为一些底层容器类型提供了一个接口,容器类型将用作模板参数(具有默认值)。

例如,std::stack 使用一些其他容器(例如,std::dequestd::liststd::vector)来保存对象,而std::stack 本身只是为您提供了一个简化/受限接口想使用堆栈操作。 std::stack 将使用的底层容器作为模板参数提供。以下是代码在标准中的样子:

namespace std {
    template <class T, class Container = deque<T> >
    class stack {
    public:
        typedef typename Container::value_type value_type;
        typedef typename Container::size_type  size_type;
        typedef Container                      container_type;
    protected:
        Container c;
    public:
        explicit stack(const Container& = Container());
        bool empty() const             { return c.empty(); }
        size_type size() const         { return c.size(); }
        value_type& top()              { return c.back(); }
        const value_type& top() const  { return c.back(); }
        void push(const value_type& x) { c.push_back(x); }
        void pop()                     { c.pop_back(); }
    };
}

当然,也许我只是误解了这个问题——如果是这样,我提前向我(有点)不同意的人道歉。

【讨论】:

  • Jerry:OP 希望编写为MyClass&lt;int, std::vector&gt; 的代码能够创建std::vector&lt;int&gt;。在std::stack 示例中,传递的类型名不是std::deque,而是std::deque&lt;T&gt;——不是类模板,而是模板类。也就是说,您的代码将导致 OP 必须执行 MyClass&lt;int, std::vector&lt;int&gt; &gt;
  • @Billy O'Neal:我已经重读了这个问题,但仍然找不到他说这就是他想要的。也许我只是昨晚没睡够或其他什么......
  • 我认为问题的重点是获取容器而不将其整个类型传递给模板参数。 (例如,我可以将值类型设为secret_type&lt;Item&gt;。)但我认为您的回答也很重要,因为我们应该解决问题,而不仅仅是回答问题,这就是我们大多数人的做法。跨度>
  • @Jerry:查看 OP 的第二个密码箱。 (看看那里的 typedef)
  • @GMan:这可能是他想要的,但我在问题中找不到任何似乎这样说的东西。我的印象很简单,他想要一个容器适配器之类的东西,允许他在/如果他认为合适的时候插入不同的底层容器。
【解决方案3】:

是和不是。

您可以使用模板模板参数,例如,

template <typename Item, template <typename> class Container>
struct TList { /* ... */ };

但是,一般来说,模板模板参数并不是特别有用,因为模板参数的数量和类型必须匹配。因此,上面的内容与std::vector 不匹配,因为它实际上有两个模板参数:一个用于值类型,一个用于分配器。模板模板参数不能利用任何默认模板参数。

为了能够使用 std::vector 模板作为参数,TList 必须声明为:

template <typename Item, template <typename, typename> class Container>
struct TList { /* ... */ };

但是,使用此模板,您将无法使用 std::map 模板作为参数,因为它有 四个 模板参数:键和值类型、分配器类型和比较器类型。

由于这种不灵活,通常避免使用模板模板参数要容易得多。

【讨论】:

  • 您能否将template &lt;typename, typename&gt; class Container 替换为typename Container,并使用Container::value_type 访问容器中包含的类型?
  • @Billy:当然;每个容器都有一个value_type typedef。但是,这将不允许您 create 容器,这是 OP 所要求的。
  • @James:啊——我明白了。看起来OP需要一些迭代器然后:)
  • 没问题,看看std::stack在做什么!
  • @Bo:嗯,std::stack 不采用模板,它采用容器类型。也就是说,它不需要std::deque,它需要std::deque&lt;T&gt;
【解决方案4】:

好吧,你可以用宏来破解它:

template <typename T, typename stl_container = std::vector<T> >
struct TList
{
    typedef stl_container Type;
};

#define TLIST(T, C) TList<T, C<T> >

TList<int> foo;
TList<int, std::list<int> > bar;
TLIST(int, std::list) baz;

【讨论】:

  • 因为我是猫爱好者 ;-)。但是,是的,这是一个 hack。
【解决方案5】:

是否可以将 std::vector 模板化并创建一个通用容器,类似的东西?

没有。您必须使用容器模板化函数或对象——您不能模板化容器本身。

例如。考虑一个典型的std::find

template<class InputIterator, class T>
InputIterator find ( InputIterator first, InputIterator last, const T& value )
{
    for ( ;first!=last; first++) if ( *first==value ) break;
    return first;
}

这适用于任何容器,但根本不需要容器的模板。

另外,鉴于您正在尝试编写独立于容器的代码,您可能想购买或借用一份Scott Meyers' Effective STL 并阅读第 2 条:当心独立于容器的代码的错觉。

【讨论】:

  • 算法不是采用迭代器,而不是容器/范围,部分原因是无法独立于容器的代码吗? 可以编写一个通用的 find 函数,它也可以有效地用于 set 和 map,只是没有这个签名。
  • @UncleBens:是的——Effective STL 中的项目阻止人们尝试只使用所有 STL 容器提供的功能,以便他们可以将它们切换出去。 (Meyers 比我更擅长解释)如果您可以通过模板对算法进行模板化,那么请务必使用它。 (但如果您希望它对序列和关联容器都最有效,您可能需要一些模板专业化;))
  • 关于第2条:谨防容器无关代码的错觉,STL中的大多数算法不都是这种精神吗?
  • @Kirakun:算法是根据迭代器实现的,而不是容器。
【解决方案6】:

您可以使用其他人在此处提到的模板模板参数。这样做的主要困难不是不同的容器类型具有不同的模板参数,而是标准允许标准容器(如vector)除了记录的必要参数之外还具有模板参数。

您可以通过提供自己的接受适当模板参数的子类类型来解决这个问题,并让任何额外的(必须具有默认值)填充到我的实现中:

template < typename T > struct simple_vector : std::vector<T> {};

或者你可以使用模板化的 typedef 习惯用法:

template < typename T > struct retrieve_vector { typedef std::vector<T> type; };

【讨论】:

  • 我投反对票,因为您继承自标准容器。它们没有虚拟析构函数,因此在您期望 vector&lt;T&gt; 的地方使用您的 simple_vector&lt;T&gt; 类是危险的;该类的用户可能(并且将会)弄错并让vector&lt;T&gt;::~vector 被调用而不是simple_vector&lt;T&gt;::~simple_vector
  • 在这种情况下不是亚历山大。给自己一两分钟时间考虑一下。或者更长...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-20
  • 2011-10-23
  • 2018-12-28
  • 1970-01-01
相关资源
最近更新 更多