【问题标题】:Appending a vector to a vector [duplicate]将向量附加到向量[重复]
【发布时间】:2010-03-31 09:33:02
【问题描述】:

假设我有 2 个标准向量:

vector<int> a;
vector<int> b;

假设两者都有大约 30 个元素。

  • 如何将向量 b 添加到向量 a 的末尾?

肮脏的方法是遍历 b 并通过 vector&lt;int&gt;::push_back() 添加每个元素,尽管我不想这样做!

【问题讨论】:

  • 我想每个人都会使用迭代器发布答案。我从来没有弄清楚为什么 vector 没有 op+=() 或 append() 函数。
  • @Neil 因为insert 就足够了?
  • @Andreas 好吧,对于 std::string 不能说同样的话吗?当然 insert() 就足够了,但是在您的回答中,实际发生的事情是一个向量被附加到另一个向量上,这一点远非显而易见。 a += b 使其透明。
  • @Andreas:在性能方面可能已经足够了,但它并不那么容易阅读。 IMO a.append(b)(甚至a+=b)会比a.insert(a.end(), b.begin(), b.end())更好地捕捉到意图。
  • @Andreas 我认为您指的是“胖接口”问题。有些类应该有胖接口,恕我直言,字符串就是其中之一——我发现 std::string 非常有用,不管纯粹主义者怎么说。我只是认为 vector 可以增加一点重量,让用户的生活更轻松,让代码的读者更清楚。

标签: c++ stl vector


【解决方案1】:
a.insert(a.end(), b.begin(), b.end());

a.insert(std::end(a), std::begin(b), std::end(b));

第二个变体是更通用的解决方案,因为b 也可以是一个数组。但是,它需要 C++11。如果您想使用用户定义的类型,请使用 ADL:

using std::begin, std::end;
a.insert(end(a), begin(b), end(b));

【讨论】:

  • insert之前需要reserve吗?
  • @VioletGiraffe 保留不是必需的,但可能是可取的。如果您重复插入一个您知道最终大小并且该大小很大的向量,那么使用保留是明智的。否则,我会让 STL 根据需要增长你的向量。
  • 如果您尝试将矢量附加到自身,此解决方案将失败。它生成具有正确大小的向量,但添加空元素而不是原始元素。并且只有在您预先添加 v.reserve(v.size()*2); 时它才会开始工作。 (但可能取决于 STL 实现)
  • @Sergey 我相信该标准明确规定给予insert 的迭代器不能与接收者对象的元素在同一范围内,所以我认为从技术上讲它是UB。
  • @Yakk 在我的 C++14 标准草案中,表 100(序列容器要求)列出了调用 a.insert(p, i, j) 的前提条件,即“i 和 j 不是 a 的迭代器。”跨度>
【解决方案2】:
std::copy (b.begin(), b.end(), std::back_inserter(a));

这可以用于向量 a 中的项目没有赋值运算符(例如 const 成员)的情况。

在所有其他情况下,与上述插入解决方案相比,此解决方案效率低下。

【讨论】:

  • 请注意,这可能比使用std::vector&lt;&gt;::insert() 效率低,因为std::copy() 无法预先预留足够的空间(它无法访问向量本身,只能一个有的迭代器),而std::vector&lt;&gt;::insert(),作为一个成员函数,可以。 (它需要弄清楚要读取的迭代器是随机访问迭代器,以便预先计算序列的长度,但这将是一个弱实现,不会这样做。)
  • 在实践中是正确的,但理论上std:: 实现者可以使其工作。他们可以在内部使用非标准扩展。
  • @MSalter:我知道实现可以做到这一点。这就是为什么我写它“可能效率较低”。理论上,实现者可以向std::back_inserter_iterator&lt;std::vector&lt;T&gt;&gt; 添加一个私有接口,以允许std::copy() 的实现识别它并使用这个私有接口来获取std::vector 并在其上调用reserve()。然而,在实践中,任何实施者都不太可能跳过所有这些障碍来优化这种极端情况。
  • @sbi 的批评是正确的,至少对于 libstdc++。 std::copy 确实比使用 std::vector::insert 慢。我刚刚使用 g++ 4.4.5 附带的 libstdc++ 对其进行了测试。
  • @Sergey,将向量附加到自身的字符串是 UB:stackoverflow.com/questions/14791984/…
【解决方案3】:

虽然说“编译器可以保留”,但为什么要依赖它?那么移动语义的自动检测呢?那么容器名称与begins 和ends 的所有重复呢?

你不想要一些更简单的东西吗?

(向下滚动至main 以获得妙语)

#include <type_traits>
#include <vector>
#include <iterator>
#include <iostream>

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

template<typename T, typename A>
struct can_reserve<std::vector<T,A>,void>:
    std::true_type
{};

template<int n> struct secret_enum { enum class type {}; };
template<int n>
using SecretEnum = typename secret_enum<n>::type;

template<bool b, int override_num=1>
using EnableFuncIf = typename std::enable_if< b, SecretEnum<override_num> >::type;
template<bool b, int override_num=1>
using DisableFuncIf = EnableFuncIf< !b, -override_num >;

template<typename C, EnableFuncIf< can_reserve<C>::value >... >
void try_reserve( C& c, std::size_t n ) {
  c.reserve(n);
}
template<typename C, DisableFuncIf< can_reserve<C>::value >... >
void try_reserve( C& c, std::size_t ) { } // do nothing

template<typename C,typename=void>
struct has_size_method:std::false_type {};
template<typename C>
struct has_size_method<C, typename std::enable_if<std::is_same<
  decltype( std::declval<C>().size() ),
  decltype( std::declval<C>().size() )
>::value>::type>:std::true_type {};

namespace adl_aux {
  using std::begin; using std::end;
  template<typename C>
  auto adl_begin(C&&c)->decltype( begin(std::forward<C>(c)) );
  template<typename C>
  auto adl_end(C&&c)->decltype( end(std::forward<C>(c)) );
}
template<typename C>
struct iterable_traits {
    typedef decltype( adl_aux::adl_begin(std::declval<C&>()) ) iterator;
    typedef decltype( adl_aux::adl_begin(std::declval<C const&>()) ) const_iterator;
};
template<typename C> using Iterator = typename iterable_traits<C>::iterator;
template<typename C> using ConstIterator = typename iterable_traits<C>::const_iterator;
template<typename I> using IteratorCategory = typename std::iterator_traits<I>::iterator_category;

template<typename C, EnableFuncIf< has_size_method<C>::value, 1>... >
std::size_t size_at_least( C&& c ) {
    return c.size();
}

template<typename C, EnableFuncIf< !has_size_method<C>::value &&
  std::is_base_of< std::random_access_iterator_tag, IteratorCategory<Iterator<C>> >::value, 2>... >
std::size_t size_at_least( C&& c ) {
    using std::begin; using std::end;
  return end(c)-begin(c);
};
template<typename C, EnableFuncIf< !has_size_method<C>::value &&
  !std::is_base_of< std::random_access_iterator_tag, IteratorCategory<Iterator<C>> >::value, 3>... >
std::size_t size_at_least( C&& c ) {
  return 0;
};

template < typename It >
auto try_make_move_iterator(It i, std::true_type)
-> decltype(make_move_iterator(i))
{
    return make_move_iterator(i);
}
template < typename It >
It try_make_move_iterator(It i, ...)
{
    return i;
}


#include <iostream>
template<typename C1, typename C2>
C1&& append_containers( C1&& c1, C2&& c2 )
{
  using std::begin; using std::end;
  try_reserve( c1, size_at_least(c1) + size_at_least(c2) );

  using is_rvref = std::is_rvalue_reference<C2&&>;
  c1.insert( end(c1),
             try_make_move_iterator(begin(c2), is_rvref{}),
             try_make_move_iterator(end(c2), is_rvref{}) );

  return std::forward<C1>(c1);
}

struct append_infix_op {} append;
template<typename LHS>
struct append_on_right_op {
  LHS lhs;
  template<typename RHS>
  LHS&& operator=( RHS&& rhs ) {
    return append_containers( std::forward<LHS>(lhs), std::forward<RHS>(rhs) );
  }
};

template<typename LHS>
append_on_right_op<LHS> operator+( LHS&& lhs, append_infix_op ) {
  return { std::forward<LHS>(lhs) };
}
template<typename LHS,typename RHS>
typename std::remove_reference<LHS>::type operator+( append_on_right_op<LHS>&& lhs, RHS&& rhs ) {
  typename std::decay<LHS>::type retval = std::forward<LHS>(lhs.lhs);
  return append_containers( std::move(retval), std::forward<RHS>(rhs) );
}

template<typename C>
void print_container( C&& c ) {
  for( auto&& x:c )
    std::cout << x << ",";
  std::cout << "\n";
};

int main() {
  std::vector<int> a = {0,1,2};
  std::vector<int> b = {3,4,5};
  print_container(a);
  print_container(b);
  a +append= b;
  const int arr[] = {6,7,8};
  a +append= arr;
  print_container(a);
  print_container(b);
  std::vector<double> d = ( std::vector<double>{-3.14, -2, -1} +append= a );
  print_container(d);
  std::vector<double> c = std::move(d) +append+ a;
  print_container(c);
  print_container(d);
  std::vector<double> e = c +append+ std::move(a);
  print_container(e);
  print_container(a);
}

hehe.

现在有了 move-data-from-rhs、append-array-to-container、append forward_list-to-container、move-container-from-lhs,感谢 @DyP 的帮助。

请注意,由于EnableFunctionIf&lt;&gt;... 技术,上面的代码不会在clang 中编译。在clang this workaround 工作。

【讨论】:

  • 我认为你可以简化这个,例如the try_reserve part
  • 你在哪里使用size_at_least? (我只能看到声明/定义,但没有调用..)
  • 人们如何使用这种语言
  • @BrainGordon 你知道上面的帖子是个笑话吗? C++ 具有图灵完备的编译时子语言,充分发挥其潜力确实经常会创建只写代码。这个笑话的妙语在main,如果你跳过上面的代码沙拉,它的可读性令人震惊:幽默是这“更简单”,这与案例相去甚远。不可读的代码沙拉所做的是向语言添加命名运算符:C++ 中不支持命名运算符,因此它通过奇怪的技巧来实现。它也写得不好:从那以后我变得更好了。
【解决方案4】:

如果您想将矢量添加到自身,两种流行的解决方案都会失败:

std::vector<std::string> v, orig;

orig.push_back("first");
orig.push_back("second");

// BAD:
v = orig;
v.insert(v.end(), v.begin(), v.end());
// Now v contains: { "first", "second", "", "" }

// BAD:
v = orig;
std::copy(v.begin(), v.end(), std::back_inserter(v));
// std::bad_alloc exception is generated

// GOOD, but I can't guarantee it will work with any STL:
v = orig;
v.reserve(v.size()*2);
v.insert(v.end(), v.begin(), v.end());
// Now v contains: { "first", "second", "first", "second" }

// GOOD, but I can't guarantee it will work with any STL:
v = orig;
v.reserve(v.size()*2);
std::copy(v.begin(), v.end(), std::back_inserter(v));
// Now v contains: { "first", "second", "first", "second" }

// GOOD (best):
v = orig;
v.insert(v.end(), orig.begin(), orig.end()); // note: we use different vectors here
// Now v contains: { "first", "second", "first", "second" }

【讨论】:

  • 除了最后一个之外,您的所有建议都是错误的,如其他帖子中所述(insert 不得将迭代器放入其操作的容器中,copy 的迭代器因插入而失效通过back_inserter)。您标记为“好”的两个似乎有效,因为没有重新分配(因为您的 reserve 电话)。最后一个是要走的路。另一个实际上允许避免第二个容器的选项是使用调整大小而不是保留,然后将向量的前半部分复制到新分配的元素。
猜你喜欢
  • 2013-08-11
  • 2018-10-02
  • 2012-04-27
  • 1970-01-01
  • 2015-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多