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

C++ 为什么std::vector需要运算符=,c++,stl,C++,Stl,我有一个关于一个可以存储在vector中的类的问题。 可以存储在向量中的需求是什么? 似乎这样的类必须有赋值运算符。但我不确定这是否是全部 让我给你举个例子。类具有常量int成员。如果我不写运算符=,它就不会编译。但是在这个例子中,这个操作符什么也不做。该程序正确显示10和20。看起来运算符=是必需的,但实际上并未使用 #include <iostream> #include <vector> class A { public: A(int a) : a_(a)

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

让我给你举个例子。类具有常量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();
}
#包括
#包括
甲级{
公众:
A(inta):A_u(A){
A&operator=(const A&a2){return*this;}//如果没有这个,编译将失败。
void print()常量{

这可能会让你大吃一惊:

v.push_back(A(20));
v.push_back(A(10));
std::sort(begin(v), end(v));
向量本身的某些方面需要可分配性,尽管我不知道,但我不能通过编译您的代码来判断,因为当我删除
操作符=()
)时,我的编译器不会抱怨,元素必须是
可复制的
可分配的


编辑:一天后再回到这个问题,当
std::vector
需要
Assignable
-任何时候它必须移动元素时,这一点似乎都非常明显。添加对
v.insert()
v.erase()的调用例如,
,编译将失败。

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

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

const int a_;
默认编译器生成的运算符=()无法将值分配给常量成员。因此,您必须重载自己:

A & A::opeartor=(const A & in)
{
    *const_cast<int*>(&a_) = in.a_; // !!!you are expected to do this.
}
A&A::opeartor=(常数A&in)
{
*const_cast(&a_)=in.a_;/!!!您应该这样做。
}

因此,您的A::operator=()版本不执行任何操作,尽管使代码编译,但不会更改左侧操作数的A值,

推回向量将使向量在内存中增长,这意味着 旧对象需要通过赋值运算符复制到新对象= 因此需要赋值运算符=

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

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

您的示例有一个隐式删除的复制构造函数,但如果手头有符合C++11标准的库,则仍应编译。 在您的示例中,对
向量所使用的类型施加约束的唯一表达式是
push_back

带有分配器
a
value\u-type
的序列容器类型
X
push\u-back()
方法要求
T
为:

  • 如果传递了左值或常量值引用,则CopyInsertable
  • 如果传递了非常量值,则MoveInsertable
这意味着它需要一个有效的复制构造函数或(在本例中)一个有效的移动构造函数,该构造函数将从代码中隐式出现。因此,在任何具有有效C++11标准库的编译器中,编译都不会失败

要求
向量中包含的类型可赋值的操作:
辅助条件

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

您需要一个复制构造函数或移动构造函数。它在VS2012和Gcc 4.8上编译,后者编译r你在使用吗?@Rapptz我发现你需要复制构造函数和复制赋值,或者移动构造函数和移动赋值。VS2012在内部使用复制/移动构造函数。GCC似乎想要复制/移动赋值。@Mysticial Works@Rapptz-Hmm…我在
std::vector
中有一个对象具有移动构造函数,但是没有移动分配。它是在VS2012中编译的,但是链接器在GCC中出错(特别是因为缺少移动分配)。虽然我怀疑这很重要,但我认为我使用的是GCC 4.6。谢谢。我理解它没有排序。这完全有道理。为什么push_back失败?@zorio:
push_back()
没有失败。我只是在调用
sort()之前显示内容没有排序
。我使用了clang。这种行为看起来依赖于实现。而且需求是可复制和可分配的。考虑到您的代码片段,这些需求是有意义的。谢谢。它在编译时没有在GCC和clang上定义运算符=。这实际上是由复制构造函数完成的,可能是在使用保留功能p时ush_back可能会这样做(我不确定这种情况),但是当您使用指定数量的元素构造向量(使用带有size_t参数的向量构造函数)并使用[]获取引用并为其赋值时,会使用操作符=1。某些算法(如前面提到的排序)也可能使用操作符=1。
操作符=()
定义确实丢失了,因为它被“删除”。2.隐式
运算符=
始终是“正确”的。它并不意味着要做你期望的事情,但这可能是你期望中的错误,而不是隐式赋值运算符。3.你不应该做这样的
常量(!!!)。如果可能的话应该避免。我不知道你为什么否决这个答案。我说过“默认实现的正确性是另一回事”。我没有做这种常量强制转换的工作。这是为了解决用户在想要对对象执行赋值操作时声明常量成员的原始问题。他实际上不想执行赋值,也没有
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
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