【问题标题】:What's the canonical way of overloading the compound assignment operator?重载复合赋值运算符的规范方法是什么?
【发布时间】:2014-10-18 19:21:55
【问题描述】:

这似乎是一个棘手的问题,因为您无法向向量添加新的成员函数。这种形式避免了最少的副本:

std::vector<T>& operator+=(std::vector<T>& lhs, const std::vector<T>& rhs)

但自分配失败,因此唯一适用于自分配的是:

template <typename T>
std::vector<T>& operator+=(std::vector<T>& lhs, std::vector<T> rhs)
{
    lhs.insert(lhs.end(), rhs.begin(), rhs.end());
    return lhs;
}

但这需要额外的副本。这样做的正确方法是什么?

在我的问题中,上述形式“不起作用”是模棱两可的,因为它们似乎适用于整数(尽管不适用于 std::strings)。有人指出这是因为它是未定义的行为。

【问题讨论】:

标签: c++ c++11


【解决方案1】:

问题:

template <typename T>
std::vector<T>& operator+=(std::vector<T>& lhs, const std::vector<T>& rhs)
{
    lhs.insert(lhs.end(), rhs.begin(), rhs.end());
    return lhs;
}

不是签名,它会将迭代器传递给insert,这些迭代器在插入完成之前变得无效。

只需使用the correct technique for appending a vector to itself,无需额外复制。

template <typename T>
void concat_in_place(std::vector<T>& lhs, const std::vector<T>& rhs)
{
    auto left_count = lhs.size();
    auto right_count = rhs.size();
    lhs.resize(left_count + right_count);
    std::copy_n(rhs.begin(), right_count, lhs.begin() + left_count);
}

【讨论】:

  • +1:它是正确的并且实际上回答了这个问题。唯一能让这个完美的事情是对std::vector::insert 行为进行标准引用。
  • 那么,添加对 reserve 的调用算作正确的技术吗?
  • @MarcGlisse:请点击链接。
  • 但是如果你把rhs.xxx()换成addressof(*rhs.xxx())就可以了?
  • @MarcGlisse:如果您使用指针并调用了reserve,我无法想象实现会以任何方式中断,但正式地,标准说迭代器、指针和引用都变得无效除非它们在插入点之前。
【解决方案2】:

根据政策,您不应该在两个不同的std 类型之间使用运算符重载它们。可能是未定义的行为:标准不明确。

如果您想在 std 容器上使用运算符语法,我建议使用命名运算符。它也更清楚,因为向量上的运算符可以是容器运算符或元素运算符,这就是默认情况下缺少它们的原因。 v +append= v2; 显然是附加的。 (创建一个静态追加对象,并用你的向量重载它的 lhs 和 rhs 运算符,并在中间步骤中使用表达式模板)

// mini named operator library.  Only supports + for now:
template<class Kind>
struct named_operator {};
template<class OP, class LHS> struct plus_ {
  LHS lhs;
  template<class RHS>
  decltype(auto) operator=(RHS&&rhs)&&{
    return plus_assign(std::forward<LHS>(lhs), OP{}, std::forward<RHS>(rhs));
  }
  template<class RHS>
  decltype(auto) operator+(RHS&&rhs)&&{
    return plus(std::forward<LHS>(lhs), OP{}, std::forward<RHS>(rhs));
  }
};
template<class Tag, class LHS>
plus_<Tag,LHS> operator+( LHS&& lhs, named_operator<Tag> ) {
  return {std::forward<LHS>(lhs)};
}

// creating a named operator:
static struct append_tag:named_operator<append_tag> {} append;

// helper function, finds size of containers and arrays:
template<class T,std::size_t N>
constexpr std::size_t size( T(&)[N] ) { return N; }
template<class C>
constexpr auto size(C&& c)->decltype(c.size()) { return c.size(); }

// implement the vector +append= range:
template<class T, class A, class RHS>
std::vector<T,A>& plus_assign(std::vector<T,A>&lhs, append_tag, RHS&& rhs) {
  auto rhs_size = size(rhs);
  lhs.reserve(lhs.size()+rhs_size);
  using std::begin; using std::end;
  copy_n( begin(rhs), rhs_size, back_inserter(lhs) );
  return lhs;
}
// implement container +append+ range:
template<class LHS, class RHS>
LHS plus( LHS lhs, append_tag, RHS&& rhs ) {
  using std::begin; using std::end; using std::back_inserter;
  copy_n( begin(rhs), size(rhs), back_inserter(lhs) );
  return std::move(lhs);
}

live example

请注意,std::vector&lt;int&gt; +append= std::list&lt;int&gt; +append+ std::array&lt;double, 3&gt; 与上述代码一起使用。

【讨论】:

  • 你不需要测试身份;请参阅我的答案中的concat_in_place 代码 sn-p。它对于自引用和两个独立向量都是正确的,并且对于两个独立向量也可能比原始代码更快。
  • @BenVoigt 好点。在此过程中,我将编写另一个名为 operator mini 库的迭代。它变得非常紧凑。虽然标签类型中的static 方法很可爱,但我想下次我将使用plus( LHS, append_tag, RHS )plus_assign( LHS, append_tag, RHS ) 类型的3 个无参数函数,因为这可以让人们为append 添加新的含义远离标签定义的地方。
  • @BenVoigt 是的,使用 3 个参数 plus 而不是 static 方法更简洁。已添加到帖子!
猜你喜欢
  • 1970-01-01
  • 2011-02-21
  • 2020-12-18
  • 1970-01-01
  • 1970-01-01
  • 2020-03-23
  • 1970-01-01
  • 1970-01-01
  • 2018-05-13
相关资源
最近更新 更多