我有一个类,其中包含一个容器和一个该容器的迭代器。我应该如何正确实现移动构造函数?我记得根据标准,你不能依赖于移动后迭代器仍然有效(这太愚蠢了)。是否有一些方法可以“更新”迭代器,如果它被使无效或其他原因?还是我必须动态分配容器,移动它,然后通过这种方式使迭代器保持有效?
std::unique_ptr
作为容器的持有者是规范的通用解决方案 - 只需转移所有权并交换迭代器,而不要移动容器。正如您已经提到的,您可以将其作为优化特殊处理,尽管我希望通用解决方案也非常高效,并且只接受在证明它对您的用例真正有性能优势之后才会增加更多复杂性(也就是潜在错误)。
我将保留原先的答案以供后续读者参考:请阅读它和评论,了解为什么其他解决方案实际上无法工作,在哪些情况下它们会引起麻烦。
更新迭代器的明显方式是:
Container c = ...;
Container::iterator it = ...;
const auto d = std::distance( c.begin(), it );
Container n = std::move(c);
it = n.begin();
std::advance( it, d );
当迭代器是随机访问迭代器时,容器通常是线性的,但是常数是恒定的。
由于您可能不想这样做,因此有两种选项可以帮助您:要么默认构造新容器并在不使迭代器失效的情况下使用 swap
,要么将容器放入 std::unique_ptr
中并移动它。
第一种方法(swap
)需要两个实例都具有容器实例,而这可能比存储在 std::unique_ptr
中的简单单指针稍大。当您经常移动实例时,我认为基于 std::unique_ptr
的方法似乎更可取,尽管每个访问都需要一个额外的指针间接引用。请自行评估(和测量),看看哪种方案最适合您的情况。
unique_ptr
,但是我可以针对所有随机访问容器进行特殊处理,并采用“distance”技术。这应该是一个不错的开始。 - Puppystd::array
和可能的其他情况是正确的,我已经更新了我的答案。谢谢! - Daniel Freystd::array
有很多特殊情况。一个行为类似于 std::array
的非标准容器可能无法满足容器的要求,因为它不是 std::array
,因此不包含这些特殊情况。 - dypstd::array
之外的所有容器,以下操作都应该有效:template<class Container>
struct foo_base
{
Container c;
Container::iterator i;
foo_base(foo_base&& rhs, bool is_end)
: c( std::move(rhs.c) )
, i( get_init(is_end, rhs.i) )
{}
Container::iterator get_init(bool is_end, Container::iterator ri)
{
using std::end; // enable ADL
return is_end ? end(c) : ri;
}
};
template<class Container>
struct foo : private foo_base<Container>
{
foo(foo&& rhs)
: foo_base(std::move(rhs), rhs.i == end(rhs.c))
{}
};
end()
迭代器可能无效;必须在容器移动之前执行此检查。但是,如果您可以确保分配器会传播(或者对于您的情况,移动赋值不会使迭代器失效),则可以使用下面更简单的版本,将swap
替换为移动赋值。get_init
函数的唯一目的是启用ADL。可能foo_base
有一个成员函数end
,这会禁用ADL。using-declaration停止未限定的查找以查找可能的成员函数,因此始终执行ADL。如果您舒适地放弃ADL,也可以使用std::end(c)
并摆脱get_init
。swap
的显式保证。为此,您可以使用:template<class Container>
struct foo
{
Container c;
Container::iterator i;
foo(foo&& rhs)
{
using std::end; // enable ADL
bool const is_end = (rhs.i == end(rhs.c));
c.swap( rhs.c );
i = get_init(is_end, rhs.i);
}
Container::iterator get_init(bool is_end, Container::iterator ri)
{
using std::end; // enable ADL
return is_end ? end(c) : ri;
}
};
swap
函数,但无法确定是否可以移动元素:"任何使用swap()
函数交换容器中元素的引用、指针或迭代器都会失效。[注意:end()
迭代器不引用任何元素,因此可能会失效。—结尾注释]" [container.requirements.general]/10 - dypstd::array
,它就不那么愚蠢了。在其他一些情况下,标准允许容器的移动构造函数具有线性复杂度,例如当分配器发生变化时。另一个例子可能是包含嵌入式第一个/最后或根节点的容器。 - Daniel Freylist
和string
,但我认为我可以为它们两个提出论据。 - Howard Hinnantmove
”。已更正。另外,你是怎么判断我指的是哪个std::move
的——是在<utility>
中只获取右值的那个,还是在<algorithm>
中执行 0 或多个移动的那个?;-) - Steve Jessoparray
。那么问题的正确答案是“mu”。 - Steve Jessop