为什么std::vector需要运算符=

Why std::vector requires operator =

本文关键字:运算符 vector std 为什么      更新时间:2023-10-16

我有一个关于可以存储在vector中的类的问题。可以存储在矢量中的要求是什么?似乎这样的类必须有赋值运算符。但我不确定这是否就是全部。

让我给你举个例子。类A具有const int成员。如果我不写运算符=,它就不会编译。但是在这个例子中,这个操作符什么也不做。此程序正确显示10和20。看起来运算符=是必需的,但在实际中没有使用。

#include <iostream>
#include <vector>
class A {
 public:
  A(int a) : a_(a) {}
  A& operator =(const A& a2) { return *this;} // Without this, compile fails.
  void print() const {
    std::cerr << a_ << std::endl;
  }
 private:
  const int a_;
};
int main(int argc, char** argv) {
  std::vector<A> v;
  v.push_back(A(10));
  v.push_back(A(20));
  for (const A& a : v) a.print();
}

这可能会让您大吃一惊:

v.push_back(A(20));
v.push_back(A(10));
std::sort(begin(v), end(v));

向量本身的某些方面需要可赋值性,尽管我不知道,但这一点我无法通过编译您的代码来判断,因为当我删除operator=()时,我的编译器不会抱怨)。根据维基百科(引用了'03标准的相关部分),元素必须是CopyConstructibleAssignable

编辑:一天后再回到这一点,当std::vector需要Assignable时——无论何时,它都必须四处移动元素,这似乎是非常明显的。例如,如果添加对v.insert()v.erase()的调用,则编译将失败。

如果我不写运算符=,它就不会编译。

这让我很惊讶,所以我查看了一下标准,发现:

您的示例有一个隐式删除的副本构造函数,但如果手头有一个符合C++11标准的库,则仍应进行编译

在您的示例中,唯一对vector使用的类型施加约束的表达式是push_back

具有分配器Avalue_typeT的序列容器类型X<T,A>push_back()方法要求T为:

  • CopyInsertable如果传递了左值或常量右值引用
  • MoveInsertable如果传递了非常值

这意味着它需要一个有效的复制构造函数,或者(在本例中)一个从代码中隐式存在的有效的移动构造函数。因此,在任何具有有效C++11标准库的编译器中,编译都不应该失败。

要求vector中包含的类型可赋值的操作:

附属条件

typdef std::vector<T> X;
X a,b;
X&& rv;
X::value_type t;
X::value_type&& u;
X::size_type n;
X::const_iterator p,q; // p = valid for a, q = valid and dereferencable
initializer_list<T> il;
[i,j) -> valid iterator-range

悲观的*操作列表

如果X是vector,则要求T可赋值的操作为:

Statement              Requirement on T
a = b;                 CopyInsertable, CopyAssignable
a = rv;                MoveInsertable, MoveAssignable
a = il;                CopyAssignable
a.emplace(p, args);    MoveInsertable, MoveAssignable
a.insert(p, t);        CopyAssignable
a.insert(p, u);        MoveAssignable
a.insert(p, n, t);     CopyInsertable, CopyAssignable
a.insert(p, i, j);     EmplaceConstructible[from *i], MoveInsertable, MoveAssignable
a.insert(p, il);       -> a.insert(p, il.begin(), il.end());
a.erase(q);            MoveAssignable
a.erase(q1,q2)         MoveAssignable
a.assign(i,j);         Assignable from *i
a.assign(il);          -> a.assign(il.begin(), il.end());
a.assign(n,t)          CopyAssignable

*=悲观意味着可能存在若干要求实际生效的某些条件。如果使用上面列出的表达式之一,则可能需要类型T是可赋值的。

对vector的push_back将使vector在内存中增长,这意味着需要通过赋值运算符将旧对象复制到新对象=因此您需要赋值运算符=。

如果不创建自己的副本构造函数和赋值运算符,编译器将保证它们的可用性。(默认实现的正确性则是另一回事)。在您的示例中,几乎不必重载A::opeartor=()。

这里的问题不是"缺少"运算符=(),而是不能使用默认的运算符,因为您已经声明了一个常量数据成员

const int a_;

默认编译器生成的运算符=()无法为常量成员赋值。所以你必须让自己超负荷:

A & A::opeartor=(const A & in)
{
    *const_cast<int*>(&a_) = in.a_; // !!!you are expected to do this.
}

因此,您的A::operator=()版本虽然使代码编译,但不会更改左手操作数的A_值

相关文章: