【问题标题】:Why std::vector requires operator =为什么 std::vector 需要运算符 =
【发布时间】:2013-07-21 09:34:05
【问题描述】:

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

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

#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();
}

【问题讨论】:

  • 你需要一个拷贝构造函数或者一个移动构造函数。
  • 它在VS2012和Gcc 4.8上编译,你用的是哪个编译器?
  • @Rapptz 我实际上发现你需要复制构造函数和复制赋值,或者移动构造函数和移动赋值。 VS2012 在内部使用复制/移动构造函数。 GCC 似乎想要复制/移动分配。
  • @Mysticial Works fine for me
  • @Rapptz 嗯...我在 std::vector 中有一个对象,它具有移动构造函数,但没有移动分配。它在 VS2012 中编译,但在 GCC 中出现链接器错误(特别是因为缺少移动分配)。虽然我怀疑这是否重要,但我认为我使用的是 GCC 4.6。

标签: c++ stl


【解决方案1】:

这可能会让你大吃一惊:

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

vector 本身的某些方面需要可分配性,尽管我不知道是哪一个(我无法通过编译您的代码来判断,因为当我删除 operator=() 时我的编译器不会抱怨)。根据Wikipedia(引用'03 标准的相关部分),元素必须是CopyConstructibleAssignable

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

【讨论】:

  • 谢谢。我知道它没有排序。这完全有道理。为什么 push_back 失败了?
  • @zorio: push_back() 不会失败。我只是表明在调用 sort() 之前内容没有排序。
  • 我用过clang。该行为看起来依赖于实现。并且要求是 CopyConstructible 和 Assignable。考虑您的代码 sn-p,这些要求是有道理的。谢谢。
  • 它在 GCC 和 clang 上没有定义 operator= 的情况下编译。
【解决方案2】:

如果我不写 operator =,它就不会编译。

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

您的示例具有隐式删除的复制构造函数,但如果手头有符合 C++11 标准的库,则仍应编译。

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

带有分配器Avalue_typeT的序列容​​器类型X&lt;T,A&gt;push_back()方法要求T为:

  • CopyInsertable 如果传递了左值或 const 右值引用
  • 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 可能需要可赋值。

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 这实际上是由复制构造函数完成的,也许在使用保留功能时 push_back 可能会这样做(我不确定这种情况),但是当你用指定数量的元素(使用带有 size_t 参数的向量构造函数)并使用 [] 获取引用并为其分配值,还有一些算法(如已经提到的排序)可能使用 operator=
    【解决方案4】:

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

    这里的问题不是“缺少” operator=(),而是不能使用默认的,因为你声明了一个 const 数据成员

    const int a_;
    

    默认编译器生成的 operator=() 不能为 const 成员赋值。所以你必须超载自己:

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

    因此,您的 A::operator=() 版本什么都不做,虽然使代码编译,但不会更改左侧操作数的 a_ 值,

    【讨论】:

    • 1. operator=() 定义确实缺失,因为它已“删除”。 2. 隐式operator= 始终是“正确的”。这并不意味着做你期望的事情,但这可能是你期望的错误,而不是隐式赋值运算符。 3. 你期望你这样做const_cast stuff(!!!)。如果可能的话,应该避免。
    • 不知道你为什么不赞成这个答案。我确实说过“默认实现的正确性是另一回事”。我没有做这样的 const_cast 东西。这是为了解决用户在想要对对象进行赋值操作时声明一个 const 成员的原始问题。
    • 他实际上并不想做分配,也没有问“如何分配 const 成员”。他只是想知道为什么将他的类存储在向量中会导致需要定义赋值运算符。此外,编译器接受赋值运算符不需要进行 const_cast 赋值。 OP写了一个,它很合适。我怀疑它“不改变左侧操作数的 a_ 值”的事实是在这里被集成的。
    • const_cast 是无效代码,纯粹而简单:在最初声明为 const 的对象上丢弃 const 是未定义的行为,您的程序可能会无限崩溃如果你这样做,会有很多令人惊讶的方式。你不是“应该这样做”:你是不允许这样做的,如果你这样做了,那你就倒霉了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-19
    • 2019-07-10
    • 1970-01-01
    • 2015-04-16
    • 1970-01-01
    • 2013-01-29
    • 1970-01-01
    相关资源
    最近更新 更多