【问题标题】:Adding element to back of STL container将元素添加到 STL 容器的背面
【发布时间】:2013-10-23 21:57:29
【问题描述】:

我正在寻找一种将元素添加到 STL 容器背面的通用方法。我希望代码支持尽可能多类型的 STL 容器。下面这段代码演示了我的问题:

#include <vector>
#include <string>

using namespace std;

template<typename T>
class S {
  T built;
  typename T::iterator built_it;
public:
  S() : built{}, built_it{built.end()} {}
  void add_to(typename T::value_type e) {
    built.emplace(built_it, e);
    ++built_it;
  }
  const T& get() {
    return built;
  }
};

int main()
{ 
  S<std::vector<int>> e;
  S<std::string> f;
  e.add_to(3);   // works
  f.add_to('c'); // doesn't
}

这里的问题很微妙。这段代码非常适合vectors,因为std::vector 实现了emplace 函数。但是std::string 没有!是否有更通用的方法来执行相同的操作?

【问题讨论】:

  • 为什么不用push_back 而不是emplacepush_back 受到两者的支持,并且似乎更符合您想要做的事情。
  • push_back 不受 std::list 之类的支持。我想这可能是一个有效的妥协。
  • push_back 支持 std::list: cplusplus.com/reference/list/list/push_back
  • 呃,哎呀!谢谢,我会试试这个,看看我能走多远。

标签: c++ c++11 stl c++14


【解决方案1】:

generic way(不一定是最有效的方式)是:

c.insert( c.end(), value );

当然,value 需要适合容器c(您可以使用decltype(c)::value_type)。如果是关联容器,例如map,是std::pair

这适用于 所有 标准容器,std::forward_list 除外。对于某些容器,该元素会在末尾添加,对于某些容器,c.end() 只是一个提示,可能会被忽略。


作为 cmets 的后续,这里是高级的东西 ;)

当您想将 已知数量的元素 插入给定容器 c(类型为 C)并且您希望至少有点效率时,您应该检测容器是否type 支持reserve() 并在插入元素之前调用它。

以下方法detects reserve() correctly(链接说明如何):

template< typename C, typename = void >
struct has_reserve
  : std::false_type
{};

template< typename C >
struct has_reserve< C, std::enable_if_t<
                         std::is_same<
                           decltype( std::declval<C>().reserve( std::declval<typename C::size_type>() ) ),
                           void
                         >::value
                       > >
  : std::true_type
{};

现在您可以将它与std::enable_if_t 一起使用,以选择性地保留空间。一个示例可能如下所示:

template< typename C >
std::enable_if_t< !has_reserve< C >::value >
optional_reserve( C&, std::size_t ) {}

template< typename C >
std::enable_if_t< has_reserve< C >::value >
optional_reserve( C& c, std::size_t n )
{
  c.reserve( c.size() + n );
}

template< typename C, typename T, std::size_t N >
void add_array( C& c, const std::array< T, N >& a )
{
  optional_reserve( c, N );
  for( const auto& e : a ) {
    c.insert( c.end(), typename C::value_type( e ) ); // see remark below
  }
}

add_array 现在可以与所有标准容器一起调用(std::forward_list 除外),它将为 std::vector 和无序关联容器调用 reserve()

由于上述内容不需要对特定容器类型进行显式特化或重载,因此它也适用于非标准容器,只要它们的接口设计合理地类似于标准容器的接口即可。 (其实我过去也有好几个这样的“自制”容器和上面的 Just-Works™)

关于上面代码中转换的说明:将Ts 转换为C::value_type 的原因只是为了表明如果需要,这将是正确的位置。在上面的示例中,它可能看起来是多余的,但在我的实际代码中,我调用了一个特殊的转换特征类来将 es(编码字符串)转换为任何容器的正确值类型。

【讨论】:

  • 也适用于不那么标准的字符串容器。 :) 我怀疑它不会比 push_back() 有一个像样的编译器和库效率低
  • @thirtythreeforty 它不再是通用的,因为std::liststd::set 完全不同。您不能在所有情况下都简单地推进迭代器。 (我看到你已经发现了std::vector 的问题:)但是我不会太担心性能,如果你需要通用代码,上面的c.end() 是要走的路。
  • 编译器几乎肯定会内联 c.end() 所以它相当于直接到达容器的结束指针或大小值 - 但是它已经实现,不太可能涉及函数调用并且试图自己保存这些信息只是重复信息和可能的错误来源
  • @thirtythreeforty 其实如果你关心性能,你想做一件事:如果你知道要添加多少元素,检测容器是否有reserve()并提前调用.检测reserve(),可以参考the first question I asked on SO and where it all started for me, and the answer for it。事实上,我正在研究这个确切的问题:如何用值填充标准容器。
  • @thirtythreeforty 我承认它可能有点太高级了。我可以编辑答案并添加一些如何使用它的示例,只需在此处留下另一条评论以防万一。不过现在不会发生,因为我现在要睡觉了:)
【解决方案2】:

大多数情况下,人们使用特质。

许多 boost 库都解决了同样的问题,因此您也许可以重用现有的特征。

一个简单的演示:Live on Coliru

#include <vector>
#include <set>
#include <string>

namespace traits
{
    template <typename Container, typename Enable = void>
        struct add_at_end;

    template <typename... TAs>
        struct add_at_end<std::vector<TAs...> > 
        {
            using Container = std::vector<TAs...>;

            template <typename... CtorArgs>
            static void apply(Container& container, CtorArgs&&... args) {
                container.emplace_back(std::forward<CtorArgs>(args)...);
            }
        };

    template <typename... TAs>
        struct add_at_end<std::set<TAs...> > 
        {
            using Container = std::set<TAs...>;

            template <typename... CtorArgs>
            static void apply(Container& container, CtorArgs&&... args) {
                container.insert(container.end(), { std::forward<CtorArgs>(args)...});
            }
        };

    template <typename... TAs>
        struct add_at_end<std::basic_string<TAs...> > 
        {
            using Container = std::basic_string<TAs...>;

            template <typename... CtorArgs>
            static void apply(Container& container, CtorArgs&&... args) {
                container.insert(container.end(), { std::forward<CtorArgs>(args)...});
            }
        };
}

template <typename Container, typename... CtorArgs>
    void add_to(Container& container, CtorArgs&&... args) {
        traits::add_at_end<Container>::apply(container, std::forward<CtorArgs>(args)...);
    }

int main()
{
    using X = std::pair<int, std::string>;

    std::vector<X> v;
    std::set<X>    s;
    std::wstring   wstr;
    std::string    str;

    add_to(v, 12, "hello");
    add_to(s, 42, "world");
    add_to(wstr, L'!');
    add_to(str, '?');
}

基本上,您所做的是拥有一个独立的实用函数add_to,它使用一个可以专门化的特征类traits::add_at_end(在这种情况下,对于任何vector&lt;...&gt;set&lt;...&gt;basic_string&lt;...&gt;模板实例。

实际上,您可以通过继承通用实现来共享类似容器(例如dequevector)的实现。

【讨论】:

  • 我不明白该代码的作用,尽管我认为我了解特征的概念。你能解释一下代码吗?我要寻找的特质是什么?
  • 查看带有一些解释的扩展答案(现在还显示了字符串类型的特化)。另见直播:coliru.stacked-crooked.com/a/b560a19849ac3057
  • 有趣。 (我还看到using 是 C++ 的一种#DEFINE;我也不知道!)我要研究这个;它可能最终是必要的。 STL 是否提供任何框架来设置它?
  • @thirtythreeforty using 是 c++11 模板别名,它比 typedef 更灵活(但 typedef 在这里可以)。标准库附带了相当数量的type_traits,您可以使用它们来组成您自己的特征:en.cppreference.com/w/cpp/header/type_traits
【解决方案3】:

push_backstd::stringstd::vectorstd::list 支持。有了这个,你的类模板很简单:

template<typename T>
class S {
  T built;
public:
  S() : built{} {}
  void add_to(typename T::value_type e) {
    built.push_back(e);
  }
  const T& get() {
    return built;
  }
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-10-01
    • 1970-01-01
    • 2015-01-09
    • 1970-01-01
    • 1970-01-01
    • 2013-12-14
    • 2020-05-28
    相关资源
    最近更新 更多