move_iterator 是做啥用的
Posted
技术标签:
【中文标题】move_iterator 是做啥用的【英文标题】:What is move_iterator formove_iterator 是做什么用的 【发布时间】:2015-03-18 13:00:01 【问题描述】:如果我理解正确,a=std::move(b)
将引用 a 绑定到 b 的地址。而且这个操作之后b指向的内容是不保证的。
move_iterator
here的实现有这一行
auto operator[](difference_type n) const -> decltype(std::move(current[n]))
return std::move(current[n]);
但是,我认为std::move
数组中的元素没有意义。如果a=std::move(b[n])
会发生什么?
下面的例子也让我很困惑:
std::string concat = std::accumulate(
std::move_iterator<iter_t>(source.begin()),
std::move_iterator<iter_t>(source.end()),
std::string("1234"));
由于concat
本身会分配一块连续的内存来存储结果,因此不会与source
有任何重叠。 source
中的数据将被复制到 concat
但不会移动。
【问题讨论】:
“如果我理解正确,a=std::move(b)
将引用 a
绑定到 b
的地址。” 仅当这是一个声明,并且 a
是参考类型。否则,它是一个移动分配。例如。如果a
和b
是std::vector<int>
,则指向向量管理的堆存储的指针从b
复制到a
,并且b
更改为指向其他位置。
迭代器是指针的抽象。取消引用指针会产生一个左值:int i = 42; int* p = &i;
然后*i
是一个左值。对于迭代器也是如此。但这意味着算法通常会复制迭代器引用的值。 move_iterator
使 *it
返回一个右值。
【参考方案1】:
move_iterator
的目的是为算法提供输入的右值。
您的示例 auto a=std::move(b[n])
不会移动数组中的值,而是将其移出数组,这是明智的做法。
std::accumulate
中的技巧是operator+ for std::string 的定义(请记住,accumulate 的默认版本使用operator+
。它对右值参数进行了特殊优化。对于我们的案例,重载编号 7 是重要的,因为accumulate
使用表达式 init + *begin
。这将尝试重用右侧参数的内存。如果这实际上是优化还不是很清楚。
【讨论】:
你能解释一下aVector.push_back(std::move(*it))
和aVector.push_back(*std::make_move_iterator(it))
之间的区别吗?【参考方案2】:
http://en.cppreference.com/w/cpp/iterator/move_iterator 这么说:
std::move_iterator 是一个迭代器适配器,其行为与底层迭代器完全相同(必须至少是一个 InputIterator),不同之处在于取消引用会将底层迭代器返回的值转换为右值。
大多数(如果不是全部)接受范围的标准算法,将迭代器从范围的开头遍历到结尾,并对取消引用的迭代器执行操作。例如,std::accumulate
可能实现为:
template <class InputIterator, class T>
T accumulate (InputIterator first, InputIterator last, T init)
while (first!=last)
init = init + *first;
++first;
return init;
如果first
和last
是普通的迭代器(调用是
std::accumulate(source.begin(), source.end(), std::string("1234"));
,那么*first
是一个对字符串的左值引用,表达式init + *first
会调用std::operator+(std::string const&, std::string const&)
(重载1here)。
但是,如果调用是
std::accumulate(std::make_move_iterator(source.begin()), std::make_move_iterator(source.end()), std::string("1234"));
然后在 std::accumulate 内部,first
和 last
是移动迭代器,因此 *first
是一个右值引用。这意味着init + *first
改为调用std::operator+(std::string const&, std::string &&)
(重载7)。
【讨论】:
事实上,重载 7(其中 lhs 是一个左值 [string const&] 而 rhs 是一个右值 [string &&])是这样实现的:return std::move(rhs.insert(0, lhs));
为了避免复制 ( const) lhs 修改前。相反,它会修改(临时)rhs,然后从中窃取。
Overload 8(两个字符串都是临时右值)更加智能:它要么将 lhs 插入 rhs,要么将 rhs 附加到 lhs,具体取决于哪个字符串有足够的容量容纳整个结果。【参考方案3】:
如果我理解正确,
a=std::move(b)
将引用a
绑定到b
的地址。而且这个操作之后b指向的内容是不保证的。
啊,不:a
不是必要参考。上面对std::move
的使用还授予编译器调用decltype(a)::operator=(decltype(b)&&)
(如果存在)的权限:在对a
赋值时使用此类赋值运算符,不需要保留b
的值,但必须保留b
仍然处于某种健全的状态等待销毁。
但是,我认为
std::move
数组中的元素没有意义。如果a=std::move(b[n])
会发生什么?
这很有意义......它只是意味着每个数组元素可以有效地分配/移动到另一个变量,但每个元素只能分配一次。在它们被移出后,正确编写的移动构造函数或赋值运算符应该使对象处于有效但未指定的状态,这意味着您通常需要在读取它们之前再次设置它们。
我的answer here 展示了某人如何将元素从list
附加/移动到vector
。使用当前的 C++ 标准,您可以像这样直接创建 move_iterators。
下面的代码显示了如何 - 即使使用较旧的编译器/C++ 标准 - 如果您想从源迭代器范围内的元素移动,make_move_iterator
可以与 std::copy
一起使用。
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
struct X
X(int n) : n_(n)
X(const X& rhs) : n_(rhs.n_)
X(X&& rhs) : n_ rhs.n_ rhs.n_ *= -1; std::cout << "=(X&&) ";
X& operator=(X&& rhs) n_ = rhs.n_; rhs.n_ *= -1; std::cout << "=(X&&) "; return *this;
int n_;
;
int main()
std::vector<X> v2, 1, 8, 3, 4, 5, 6;
std::vector<X> v2;
std::copy(v.begin() + 2, v.end(), std::insert_iterator(v2, v2.end()));
for (auto& x : v)
std::cout << x.n_ << ' ';
std::cout << '\n';
std::copy(std::make_move_iterator(v.begin() + 2), std::make_move_iterator(v.end()), std::insert_iterator(v2, v2.end()));
for (auto& x : v)
std::cout << x.n_ << ' ';
std::cout << '\n';
输出:
2 1 8 3 4 5 6
=(X&&) =(X&&) =(X&&) =(X&&) =(X&&) 2 1 -8 -3 -4 -5 -6
代码可以在coliru上运行/编辑。
【讨论】:
现在我明白了,我认为使用 std 移动,编译器会做一些魔法来交换两个左值变量的内存。事实证明,魔术是通过采用右值的复制构造函数完成的。这意味着它不适用于普通的旧类型。例如,如果 b 是一个 int 数组,则 a=std::move(b[n]) 将与 a=b[n] 相同。 @MinLin:复制构造函数和赋值运算符是“移动魔法”最明显的用途。正如你所说 - 他们不会使int
s 上的操作更有效 - 只有在没有太多复制或分配/解除分配的情况下可以通过某种方式获得数据所有权的类型。
@MinLin:请注意,“采用右值的复制构造函数”正是我们所说的“移动构造函数”的意思。你所说的是一种不寻常但非常有趣的表达方式。 :) (虽然,还要注意,我们在这里讨论的是 assignment 运算符。所以你可以说“复制赋值运算符,它需要一个右值”,或者“移动赋值运算符”——同样,两者都很完美准确的描述符。)
remove_if
无论如何都会移动,所以我看不到收益
@Yola:现在是真的——也许 ideone(我在 2015 年使用的在线编译器)正在使用一个没有更新 remove_if 的旧编译器版本——不能确定。但是,我已经替换了这个例子来展示它如何与std::copy
结合使用。以上是关于move_iterator 是做啥用的的主要内容,如果未能解决你的问题,请参考以下文章
maven.multiModuleProjectDirectory 是做啥用的?
cursor.setNotificationUri() 是做啥用的?