【问题标题】:How to self-copy a vector? [duplicate]如何自复制向量? [复制]
【发布时间】:2013-01-24 17:34:14
【问题描述】:

假设我有一个包含“a”和“b”的vector<string>,我想复制自己 2 次,以便向量现在包含“a”、“b”、“a”、“b”、“a ", "b"

有什么比使用forpush_back 更好的方法?

【问题讨论】:

  • 只是烦人:foreach
  • emplace_back 将为您节省一份额外的副本
  • 可能使用 std::copy 会比 for 循环快
  • @PorkyBrain 只是想知道我是否可以执行 vec.reserve 之类的操作并将其 memset 两次

标签: c++ vector stl


【解决方案1】:

我最初的想法:

myvec.reserve(myvec.size()*3);  //reserve not only speeds things upt it also protects us ftom iterator invalidation
vector<string>::iterator it = myvec.end();    //we ant to add two times the origional onto the end so we save the end of the origional end
myvec.insert(myvec.end(), myvec.begin(), it);
myvec.insert(myvec.end(), myvec.begin(), it);

感谢 Emilio Garavaglia 首先指出了这方面的问题,这里有很多问题的原因:Does std::vector::insert() invalidate iterators if the vector has enough room (created through reserve)?

第二次尝试:

std::size_t size = myvec.size();
myvec.resize(size*3);  //resize must protects us from iterator invalidation
vector<string>::iterator it = myvec.begin()+size;
std::copy(myvec.begin(),it,it);
std::copy(myvec.begin(),it,it+size);

由于没有实现将实现 std::string 其默认构造函数在堆上分配一些东西,这应该会导致更少的堆访问,因此比其他示例更快。

另一个堆访问最小化是将向量复制到另一个插入它然后移入原件,我偷了 Emilio Garavaglia 代码并拉皮条:

{
vector<string> m = { "a", "b" };
auto n = m; // keep initial value safe
m.reserve(3 * m.size()); // preallocate, to avoid consecutive allocations
m.insert(m.end, n.begin(), n.end());
std::for_each(n.begin(),n.end(),[&n](std::string &in){n.emplace_back(std::move(in));});
}

【讨论】:

  • C++ 11 中是否有 auto somthing?
  • @NSF 是的,至少在目前的用法中是这样。
  • 根据标准,修改后的向量上的迭代器应始终被视为无效。您所说的在大多数实施中都是正确的,但标准并未将其设置为要求,因此我们不能相信它会一直有效。
  • @EmilioGaravaglia 是的,我刚刚查了一下,你的权利……该死的陷阱
  • 在我看来你的第一次尝试应该没问题。引用en.cppreference.com如果新的 size() 大于 capacity(),则所有迭代器和引用都无效。否则,只有添加元素之后的迭代器和引用无效。正如您最后添加的那样,这应该不是问题。
【解决方案2】:
vector<string> m = { "a", "b" };

auto n = m; // keep initial value safe

m.reserve(3 * m.size()); // preallocate, to avoid consecutive allocations
m.insert(m.end, n.begin(), n.end());
m.insert(m.end, n.begin(), n.end());

【讨论】:

  • 保存第二个插入,从 n 中交换值,这将防止您在堆上分配一堆字符串缓冲区,复制内容然后将原件扔掉。
  • +1 用于早点指出我的错误,因为我在第三个示例中窃取并仅稍微修改了您的代码
【解决方案3】:

我的第一个想法是避免迭代器无效的问题。

{   // note: nested scope
    vector<string> temp(vec); // 1st copy
    temp.insert(temp.end(), vec.begin(), vec.end()); // 2nd copy
    temp.insert(temp.end(), vec.begin(), vec.end()); // 3rd copy
    temp.swap(vec); // swap contents before temp is destroyed
}

经过审核,我认为 PorkyBrain 和 Emilio Garavaglia 的回答可能更有意义。

【讨论】:

  • Emilio Garavaglia 的答案与您的相同(reserve 呼叫除外,这是可选的)。您的额外好处是提供了更强大的异常安全保证(如果第二个 insert 失败,您的代码不会更改原始向量)所以我给它 +1 :)
  • 如果向量必须在这中间增长它会很慢,储备缺失。
  • @PorkyBrain:它是摊销的常数时间,所以我会先分析一下它是否是一个真正的瓶颈,然后再尝试野蛮优化的替代方案。
  • 大多数实现会增长 1.5 或 2 倍。因此,如果不调整大小,您的大小将增长 *= 3 倍。
【解决方案4】:

这是一个简单的方法:

template<typename T, typename A1, typename A2>
std::vector<T, A1> operator+( std::vector<T, A1> left, std::vector<T, A2> const& right ) {
  left.insert( left.end(), right.begin(), right.end() );
  return left;
}


int main() {
  std::vector<string> m = { "a", "b" );

  m = m + m + m;
}

但正如@ChristianAmmer 指出的那样,在std::vector 上覆盖operator+ 是模棱两可的。那是错误的。

因此,您可以编写完整的中缀命名运算符语法,并使用 C++ 的魔力将其嵌入 C++ 语言中,以消除这种歧义。有点像这样:

#include <utility>

template<typename Operation, short op>
struct InfixOp {
  Operation* self() { return static_cast<Operation*>(this); }
  Operation const* self() const { return static_cast<Operation const*>(this); }
};

template<typename first_type, typename Infix, short op>
struct PartialForm {
  Infix const* infix;

  first_type a;

  template<typename T>
  PartialForm(T&& first, Infix const* i):infix(i), a(std::forward<T>(first)) {}
};

#define OVERRIDE_OPERATORS(OP, CODE) \
template<\
  typename Left,\
  typename Operation\
>\
PartialForm<typename std::remove_reference<Left>::type, Operation, CODE> operator OP( Left&& left, InfixOp<Operation, CODE> const& op ) {\
  return PartialForm<typename std::remove_reference<Left>::type, Operation, CODE>( std::forward<Left>(left), op.self() );\
}\
\
template<\
  typename Right,\
  typename First,\
  typename Operation\
>\
auto operator OP( PartialForm<First, Operation, CODE>&& left, Right&& right )\
  ->decltype( (*left.infix)( std::move( left.a ), std::forward<Right>(right)) )\
{\
  return (*left.infix)( std::move( left.a ), std::forward<Right>(right) );\
}

OVERRIDE_OPERATORS(+, '+')
OVERRIDE_OPERATORS(*, '*')
OVERRIDE_OPERATORS(%, '%')
OVERRIDE_OPERATORS(^, '^')
OVERRIDE_OPERATORS(/, '/')
OVERRIDE_OPERATORS(==, '=')
OVERRIDE_OPERATORS(<, '<')
OVERRIDE_OPERATORS(>, '>')
OVERRIDE_OPERATORS(&, '&')
OVERRIDE_OPERATORS(|, '|')
//OVERRIDE_OPERATORS(!=, '!=')
//OVERRIDE_OPERATORS(<=, '<=')
//OVERRIDE_OPERATORS(>=, '>=')


template<typename Functor, char... ops>
struct Infix:InfixOp<Infix<Functor, ops...>, ops>...
{
  Functor f;
  template<typename F>
  explicit Infix(F&& f_in):f(std::forward<F>(f_in)) {}
  Infix(Infix<Functor, ops...> const& o):f(o.f) {}
  Infix(Infix<Functor, ops...>&& o):f(std::move(o.f)) {}
  Infix(Infix<Functor, ops...>& o):f(o.f) {}
  template<typename L, typename R>
  auto operator()( L&& left, R&& right ) const
    -> decltype( f(std::forward<L>(left), std::forward<R>(right)))
  {
    return f(std::forward<L>(left), std::forward<R>(right));
  }
};

template<char... ops, typename Functor>
Infix<Functor, ops...> make_infix( Functor&& f )
{
  return Infix<Functor, ops...>(std::forward<Functor>(f));
}

#include <vector>

struct append_vectors {
  template<typename T>
  std::vector<T> operator()(std::vector<T> left, std::vector<T>const& right) const {
    left.insert(left.end(), right.begin(), right.end());
    return std::move(left);
  }
};

struct sum_elements {
  template<typename T>
  std::vector<T> operator()(std::vector<T> left, std::vector<T>const& right) const {
    for(auto it = left.begin(), it2 = right.begin(); it != left.end() && it2 != right.end(); ++it, ++it2) {
      *it = *it + *it2;
    }
    return left;
  }
};
struct prod_elements {
  template<typename T>
  std::vector<T> operator()(std::vector<T> left, std::vector<T>const& right) const {
    for(auto it = left.begin(), it2 = right.begin(); it != left.end() && it2 != right.end(); ++it, ++it2) {
      *it = *it * *it2;
    }
    return left;
  }
};

#include <iostream>

int main() {
  auto append = make_infix<'+'>(append_vectors());
  auto sum = make_infix<'+'>(sum_elements());
  auto prod = make_infix<'*'>(prod_elements());

  std::vector<int> a = {1,2,3};
  a = a +append+ a +append+ a;
  a = a +sum+ a;
  a = a *prod* a;

  std::cout << a.size() << "\n";
  for (auto&& x:a) {
    std::cout << x << ",";
  }
  std::cout << "\n";
}

它的优点是在你使用它的时候很清楚(我的意思是,a = a +append+ a 很清楚它打算做什么),但代价是理解它的工作原理有点棘手,而且有点对于这样一个简单的问题,冗长。

但至少歧义消失了,这是一件好事,对吧?

【讨论】:

  • std::vector重载operator+=被认为是ambiguous,这(在我看来)也适用于operator+
  • 这和我们讨论的迭代器有同样的问题。
  • @PorkyBrain 我不这么认为。更仔细地检查left 的生命周期。
  • @ChristianAmmer 我摆脱了歧义。更好?
  • 你觉得这样更好吗?为什么它必须是运算符重载的东西?用拼写名称编写函数有什么问题(但我害怕提出建议)?为什么标准库的发明者为std::string 做了一个operator+ 而不是为std::vector?请记住,其他人正在阅读您的代码,因此意图应该很明确。
猜你喜欢
  • 2011-02-14
  • 1970-01-01
  • 2019-11-09
  • 2021-01-23
  • 2018-08-25
  • 1970-01-01
  • 2017-12-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多