- 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.
- Learning Java Functional Programming
- Python語言程序設(shè)計
- C#程序設(shè)計(慕課版)
- Monitoring Elasticsearch
- Big Data Analytics
- Learning Probabilistic Graphical Models in R
- Programming with CodeIgniterMVC
- 少兒編程輕松學(xué)(全2冊)
- Android應(yīng)用程序設(shè)計
- Python數(shù)據(jù)可視化之matplotlib實踐
- 優(yōu)化驅(qū)動的設(shè)計方法
- Hadoop Blueprints
- C語言編程魔法書:基于C11標(biāo)準(zhǔn)
- Building Web and Mobile ArcGIS Server Applications with JavaScript(Second Edition)
- Android開發(fā)權(quán)威指南(第二版)