考虑insert
的以下emplace
和std::vector<T>
成员函数:
template <class... Args> iterator emplace(const_iterator position, Args&&... args);
iterator insert(const_iterator position, const T& x);
iterator insert(const_iterator position, T&& x);
iterator insert(const_iterator position, size_type n, const T& x);
如果通过引用向量本身的元素作为参数来调用其中一个,该怎么办?通常,它们中的每一个都会使从position
开始的所有元素的引用无效,这可能包括参数,或者如果发生重新分配,则引用所有元素,这些元素肯定包含它,但这样做意味着这样的调用无效或插入(似乎)先发生?
查看一些常见的实现会产生奇怪的结果:
libstdc ++在移动任何元素之前复制参数,但仅限于const T&
的{{1}}重载。它包含以下评论:
这三个操作的顺序由C ++ 0x决定 在这种情况下,移动可能会改变属于的新元素 到现有的矢量。这只是呼叫者的问题 通过const lvalue ref获取元素(见23.1 / 13)。
但是C ++11§23.1只是容器库的简短摘要,即使我们假设这是指§23.2.1(以前是C ++ 03中的§23.1),§23.2.1 / 13只给出了分配器感知容器的定义,这似乎与此无关。我已经查看了第23章,但我没有找到任何相关的内容。
libc ++在移动insert
中的任何元素之前创建一个临时文件,而在emplace
中它首先移动元素但是将参数引用转换为指针并调整它以确保它指向原始元素元素 - 但同样,它仅在insert
重载中完成所有这些。
Visual C ++在移动任何元素之前创建副本/临时文件。
我是否错过了标准定义此行为的位置?为什么我看到的三个C ++库彼此不一致?为什么libstdc ++评论说它只是const T&
的一个问题?如果标准不要求这个工作,为什么图书馆总是懒得让它工作呢? (当然这会花费一些本来可以避免的副本和/或移动。)最后,如果我正在实现一个类似于insert(const_iterator, const T&)
的容器,我应该这样做吗?
答案 0 :(得分:8)
首先回答第二个问题:标准明确指出允许标准库假定在通过右值引用传递某些内容时,该右值引用是该对象的唯一引用。这意味着它不能合法地成为向量的元素。 C ++ 11 17.6.4.9/1的相关部分:
- 如果函数参数绑定到右值引用参数,则实现可以假设 此参数是对此参数的唯一引用。 ... [注意:如果程序将左值转换为 将左值传递给库函数时的xvalue(例如通过使用参数调用函数)
move(x)
),该程序实际上要求该函数将该左值作为临时值处理。该 实现可以自由地优化掉参数检查,如果参数是可能需要的 一个左值。 -end note ]
这使我们只处理const T &
案件。尽管libstdc ++和libc ++在这种情况下有所不同,但它们的净结果是相同的 - 它们将从传入的对象中正确复制。而标准只规定了行为,而不是实现。只要他们达到了正确的行为,他们就没事了。