- Mastering the C++17 STL
- Arthur O'Dwyer
- 780字
- 2021-07-08 10:20:23
Variations on a theme - std::move and std::move_iterator
As you might guess from the name, or you might have noticed in the preceding implementation, the std::copy algorithm works by copying elements from the input range to the output. As of C++11, you might wonder: What if instead of copying the elements, we used move semantics to move them from the input to the output?
The STL provides two different approaches to this problem. The first one is the most straightforward: there is a std::move algorithm (defined in the <algorithm> header) with the following definition:
template<class InIt, class OutIt>
OutIt move(InIt first1, InIt last1, OutIt destination)
{
while (first1 != last1) {
*destination = std::move(*first1);
++first1;
++destination;
}
return destination;
}
It's exactly the same as the std::copy algorithm except for the addition of a single std::move on the input element (be careful--this inner std::move, with one argument, defined in the <utility> header, is a completely different beast from the outer, three-argument std::move defined in <algorithm>! The fact that they share a name is unfortunate. Ironically, one of the few other STL functions to suffer a similar situation is std::remove; see the Deleting from a sorted array section, and also Chapter 12, Filesystem).
The other approach is a variation of what we saw previously with back_inserter. Rather than switching out the core algorithm, we can continue using std::copy but parameterize it differently. Suppose we passed in a new type of iterator, which (like back_inserter) wrapped around our original object and changed its behavior? In particular, we need an input iterator whose operator* returns an rvalue. We can do that!
template<class It>
class move_iterator {
using OriginalRefType = typename std::iterator_traits<It>::reference;
It iter;
public:
using iterator_category = typename
std::iterator_traits<It>::iterator_category;
using difference_type = typename
std::iterator_traits<It>::difference_type;
using value_type = typename std::iterator_traits<It>::value_type;
using pointer = It;
using reference = std::conditional_t<
std::is_reference_v<OriginalRefType>,
std::remove_reference_t<OriginalRefType>&&,
OriginalRefType
>;
move_iterator() = default;
explicit move_iterator(It it) : iter(std::move(it)) {}
// Allow constructing or assigning from any kind of move-iterator.
// These templates also serve as our own type's copy constructor
// and assignment operator, respectively.
template<class U>
move_iterator(const move_iterator<U>& m) : iter(m.base()) {}
template<class U>
auto& operator=(const move_iterator<U>& m)
{ iter = m.base(); return *this; }
It base() const { return iter; }
reference operator*() { return static_cast<reference>(*iter); }
It operator->() { return iter; }
decltype(auto) operator[](difference_type n) const
{ return *std::move(iter[n]); }
auto& operator++() { ++iter; return *this; }
auto& operator++(int)
{ auto result = *this; ++*this; return result; }
auto& operator--() { --iter; return *this; }
auto& operator--(int)
{ auto result = *this; --*this; return result; }
auto& operator+=(difference_type n) const
{ iter += n; return *this; }
auto& operator-=(difference_type n) const
{ iter -= n; return *this; }
};
// I've omitted the definitions of non-member operators
// == != < <= > >= + - ; can you fill them in?
template<class InputIterator>
auto make_move_iterator(InputIterator& c)
{
return move_iterator(c);
}
Sorry for the density of that code; trust me that you can safely skip over the details. For those who like this kind of thing, you might notice that we're providing a templated constructor from move_iterator<U> that happens to double as our copy constructor (when U is the same type as It); and that we're providing a lot of member functions (such as operator[] and operator--) whose bodies will error out for a lot of possible types of It (for example, when It is a forward iterator--see Chapter 2, Iterators and Ranges), but this is fine because their bodies won't get instantiated unless the user actually tries to call those functions at compile time (if the user actually does try to -- a move_iterator<list_of_ints::iterator>, then of course that'll yield a compile-time error).
Just as with back_inserter, notice that the STL provides a helper function make_move_iterator for the benefit of pre-C++17 compilers that don't have constructor template type deduction. In this case, as with make_pair and make_tuple, the "helper" name is uglier than the actual class name, and so I tentatively recommend using the C++17 feature in your code; why type an extra five characters and instantiate an extra function template if you don't have to?
Now we have two different ways of moving data from one container or range to another: the std::move algorithm and the std::move_iterator adaptor class. Here are examples of both idioms:
std::vector<std::string> input = {"hello", "world"};
std::vector<std::string> output(2);
// First approach: use the std::move algorithm
std::move(input.begin(), input.end(), output.begin());
// Second approach: use move_iterator
std::copy(
std::move_iterator(input.begin()),
std::move_iterator(input.end()),
output.begin()
);
The first approach, using std::move, is obviously much cleaner if moving data is all you're doing. So why did the standard library bother to provide this "messier" approach with move_iterator? To answer that question, we'll have to explore yet another algorithm that is fundamentally related to std::copy.
- MATLAB圖像處理超級學習手冊
- R語言數據可視化之美:專業圖表繪制指南
- Hands-On JavaScript High Performance
- Visual Basic程序設計實驗指導(第4版)
- FFmpeg入門詳解:音視頻原理及應用
- 深入淺出Serverless:技術原理與應用實踐
- Mastering JBoss Enterprise Application Platform 7
- WordPress 4.0 Site Blueprints(Second Edition)
- Python算法指南:程序員經典算法分析與實現
- 區塊鏈技術進階與實戰(第2版)
- 小程序,巧應用:微信小程序開發實戰(第2版)
- 深入理解BootLoader
- Python Machine Learning Blueprints:Intuitive data projects you can relate to
- Mudbox 2013 Cookbook
- 官方 Scratch 3.0 編程趣味卡:讓孩子們愛上編程(全彩)