6

我最近在 StackOverflow 上阅读了关于What is the copy-and-swap idiom 的答案?并且知道复制和交换习语可以

避免代码重复,并提供强大的异常保证。

但是,当我查看 SGI STL deque implementation时,我发现它没有使用成语。我想知道为什么不,如果成语在某种程度上像“最佳实践”?

  deque& operator= (const deque& __x) {
    const size_type __len = size();
    if (&__x != this) {
      if (__len >= __x.size())
        erase(copy(__x.begin(), __x.end(), _M_start), _M_finish);
      else {
        const_iterator __mid = __x.begin() + difference_type(__len);
        copy(__x.begin(), __mid, _M_start);
        insert(_M_finish, __mid, __x.end());
      }
    }
    return *this;
  }       
4

5 回答 5

8

除非容器需要增长,否则您显示的代码不会重新分配内存,这可以节省大量资金。复制和交换总是分配内存来进行复制,然后为现有元素释放内存。

考虑deque<vector<int>>双端队列的现有向量成员具有大容量的情况。

deque<vector<int>> d(2);
d[0].reserve(100);
d[1].reserve(100);

使用 SGI STL 实现,分配给每个元素会保留该容量,因此如果向量需要增长,它们可以在不分配任何内容的情况下这样做:

d = deque<vector<int>>(2);
assert(d[0].capacity() >= 100);
assert(d[1].capacity() >= 100);
d[0].push_back(42);  // does not cause an allocation

复制和交换将用没有备用容量的新元素替换现有成员,因此上述断言将失败,并且push_back需要分配内存。这浪费了重新分配和重新分配的时间,而不是使用已经存在的完美内存。

复制和交换是一种非常容易获得异常安全性和正确性的便捷方式,但不一定尽可能高效。在像 STL 或 C++ 标准库这样的代码中,你不想为了稍微更容易的实现而牺牲效率,并且这样的代码通常应该由能够通过“艰难的方式”来获得异常安全的专家编写“不只是最方便的方式。

于 2015-05-22T13:24:16.387 回答
4

该惯用语是一种提供强大异常保证的简单方法,因此除非您有充分的理由不这样做,否则它是一种“最佳实践”,可以说是一件好事。

但是,它有一个代价:必须创建和销毁第二个对象。这可能会很昂贵,并且如果这涉及到大量内存分配,则可能会使峰值内存使用量大大增加。如果这是一个问题,或者如果您正在编写一个像这样的通用库,您应该找到其他方法来复制对象而不创建临时对象。与使用简单习语相比,异常安全需要更加小心。

于 2015-05-22T13:27:03.867 回答
3

虽然复制和交换是最佳实践,但在某些情况下它可能会带来效率损失。在这种特定情况下,代码正在利用这样一个事实,即如果分配给的向量已经有足够的可用内存,则可以直接复制这些值而无需进行额外的内存分配。

于 2015-05-22T13:23:45.523 回答
2

我想在这种情况下这样做是为了避免过多的重新分配:复制和交换将需要分配复制(因此会立即占用双倍存储空间),然后释放所有双端队列的内存。仅当复制源较大且仅需要差异时,此代码才会分配。

这实际上适用于 deque 而不是 vector - vector::operator= 如何在那里实现?

而且这可能是在成语流行之前写的。即使是现在,它也没有被普遍接受。

于 2015-05-22T13:29:06.390 回答
1

标准规定std::deque<T>::operator=(const std::deque<T>&)应该提供基本的异常保证——即容器在异常情况下保持有效状态,而不是保证——这将支持使用复制/交换。

于 2015-05-22T13:29:35.407 回答