为什么SGI STL不使用复制和交换习惯用法?

时间:2015-05-22 13:19:37

标签: c++ stl copy-and-swap

我最近在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;
  }       

5 个答案:

答案 0 :(得分: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 ++标准库这样的代码中,您不希望为了稍微简单的实现而牺牲效率,并且这些代码通常应该由能够通过“艰难的方式”实现异常安全的专家编写。 “不仅仅是最方便的方式。

答案 1 :(得分:4)

这个成语是一种提供强大异常保证的简单方法,因此除非你有充分的理由不这样做,否则这是一个好的事情意义上的“最佳实践”。

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

答案 2 :(得分:3)

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

答案 3 :(得分:2)

我想在这种情况下,这是为了避免多余的重新分配:复制和交换将需要分配副本(因此在某个时刻进行双重存储),然后释放所有双端队列的内存。仅当copy-from较大且仅需要差异时,此代码才会分配。

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

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

答案 4 :(得分:1)

标准要求std::deque<T>::operator=(const std::deque<T>&)应提供基本例外保证 - 即在异常情况下容器处于有效状态,而不是强< / strong>保证 - 这将主张使用复制/交换。