为什么 SGI STL 不使用复制和交换习惯用法?
Why SGI STL don't use the copy-and-swap idiom?
我最近在 Whosebug 上阅读了一个关于 What is the copy-and-swap idiom? 的答案,并且知道复制和交换习语可以
avoiding code duplication, and providing a strong exception guarantee.
然而,当我查看 SGI STL deque
implementation 时,我发现它并没有使用这个习语。我想知道为什么不,如果这个成语有点像 "best practice"?
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;
}
虽然复制和交换是最佳做法,但在某些情况下它可能会降低效率。在这种特定情况下,代码利用了这样一个事实:如果分配给的向量已经有足够的可用内存,则可以只复制值而无需进行额外的内存分配。
除非容器需要增长,否则您显示的代码不会重新分配内存,这可以节省大量资金。复制和交换总是分配内存来进行复制,然后为现有元素释放内存。
考虑一个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
Copy-and-swap 会将现有成员替换为没有空闲容量的新元素,因此上述断言将失败,并且 push_back
需要分配内存。这浪费了时间去分配和重新分配,而不是使用已经存在的完美内存。
复制和交换是一种非常容易获得异常安全性和正确性的便捷方法,但不一定尽可能高效。在像 STL 或 C++ 标准库这样的代码中,您不希望为了实现稍微容易一些而牺牲效率,并且此类代码通常应该由能够通过这样做获得异常安全性的专家编写 "the hard way" 不仅是最方便的方式。
该惯用语是一种提供强大异常保证的简单方法,因此从某种意义上说,它是一件好事,除非您有充分的理由不这样做。
但是,它是有代价的:必须创建和销毁第二个对象。这可能代价高昂,并且如果这涉及大量内存分配,则可能会比必要的多得多地增加峰值内存使用量。如果这是一个问题,或者如果您正在编写一个像这样的通用库,您应该找到其他方法来复制对象而不创建临时对象。异常安全性需要比使用简单习惯用法时更加小心。
我想在这种情况下这样做是为了避免过多的重新分配:复制和交换将需要分配副本(因此立即占用双倍存储空间),然后释放所有双端队列的内存。此代码仅在复制来源较大且仅需要差异时才会分配。
这实际上适用于 deque 而不是 vector - vector::operator= 是如何实现的?
而且这可能是成语流行之前写的。即使是现在也没有被普遍接受。
标准要求 std::deque<T>::operator=(const std::deque<T>&)
应提供 基本 异常保证 - 即容器在异常情况下保持有效状态,而不是strong 保证 - 这会争论使用 copy/swap.
我最近在 Whosebug 上阅读了一个关于 What is the copy-and-swap idiom? 的答案,并且知道复制和交换习语可以
avoiding code duplication, and providing a strong exception guarantee.
然而,当我查看 SGI STL deque
implementation 时,我发现它并没有使用这个习语。我想知道为什么不,如果这个成语有点像 "best practice"?
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;
}
虽然复制和交换是最佳做法,但在某些情况下它可能会降低效率。在这种特定情况下,代码利用了这样一个事实:如果分配给的向量已经有足够的可用内存,则可以只复制值而无需进行额外的内存分配。
除非容器需要增长,否则您显示的代码不会重新分配内存,这可以节省大量资金。复制和交换总是分配内存来进行复制,然后为现有元素释放内存。
考虑一个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
Copy-and-swap 会将现有成员替换为没有空闲容量的新元素,因此上述断言将失败,并且 push_back
需要分配内存。这浪费了时间去分配和重新分配,而不是使用已经存在的完美内存。
复制和交换是一种非常容易获得异常安全性和正确性的便捷方法,但不一定尽可能高效。在像 STL 或 C++ 标准库这样的代码中,您不希望为了实现稍微容易一些而牺牲效率,并且此类代码通常应该由能够通过这样做获得异常安全性的专家编写 "the hard way" 不仅是最方便的方式。
该惯用语是一种提供强大异常保证的简单方法,因此从某种意义上说,它是一件好事,除非您有充分的理由不这样做。
但是,它是有代价的:必须创建和销毁第二个对象。这可能代价高昂,并且如果这涉及大量内存分配,则可能会比必要的多得多地增加峰值内存使用量。如果这是一个问题,或者如果您正在编写一个像这样的通用库,您应该找到其他方法来复制对象而不创建临时对象。异常安全性需要比使用简单习惯用法时更加小心。
我想在这种情况下这样做是为了避免过多的重新分配:复制和交换将需要分配副本(因此立即占用双倍存储空间),然后释放所有双端队列的内存。此代码仅在复制来源较大且仅需要差异时才会分配。
这实际上适用于 deque 而不是 vector - vector::operator= 是如何实现的?
而且这可能是成语流行之前写的。即使是现在也没有被普遍接受。
标准要求 std::deque<T>::operator=(const std::deque<T>&)
应提供 基本 异常保证 - 即容器在异常情况下保持有效状态,而不是strong 保证 - 这会争论使用 copy/swap.