仔细阅读法律:
20.2.2 交换 [utility.swap]
- 模板 void swap(T& a, T& b) noexcept(is_nothrow_move_constructible::value &&
is_nothrow_move_assignable::value);
2 要求:类型T必须是可移动构造和可移动赋值的。 (表格20) 和 (表格22)
3 效果:交换两个位置中存储的值。
- 模板 void swap(T (&a)[N], T (&b)[N]) noexcept(noexcept(swap(*a, *b)));
4 要求:对于范围[0,N)中的所有i,a[i]必须与b[i]可交换。 (17.6.3.2)
5 效果:swap_ranges(a, a + N, b)
25.3.3 交换 [alg.swap]
- 模板 void iter_swap(ForwardIterator1 a, ForwardIterator2 b);
5 效果:swap(*a, *b).
6 要求:a和b必须是可解引用的。*a必须与*b可交换。 (17.6.3.2)
因此,iter_swap需要交换存储在两个解引用位置或解引用位置范围内的值,任何试图交换引用或位置本身都是违反一致性的。这显然禁止了对std::iter_swap背后原因进行优化的猜测。相反,正如Potatoswatter正确指出的那样,封装和抽象是其存在的主要原因。std::iter_swap和std::swap属于不同的抽象层,就像std::swap本身和通过重载分辨率选择的任何二进制非成员函数“swap”不同一样。
将开发人员和设计师的角色互换,以理解实现相同结果并不意味着相同,就像“即使从基本类型声明typedef对于编译器来说只是噪音,但对于读者来说却不是噪音”一样。把它当作一个笑话,但我们可以争论整个C++只是一个可废弃的工件,包装了C,因为两者在最低层次上做着相同的事情,以及通过包装器表示另一个抽象的任何代码块都是如此。特别是当线条如此之薄时,例如std::iter_swap,“swap”和std::swap的情况。也许“using std::swap”只有几个字符,并且一旦编译就会消失,但这意味着注入标识符并构建整个重载解析机制。反复注入、构建、替换、丢弃。远非抽象、封装和可回收的方法。
通过上层公开内部工作,会增加维护时的另一个潜在故障机会。在交换域中,如果在深度元编程包含设计中缺失(或搞乱)了"using std::swap"语句,那么将在模板函数内静默等待着一个平凡可交换的基本类型或c数组类型去破坏构建,如果幸运的话,否则就可能导致无限递归而最终引起栈溢出(TM)。显然,可扩展机制的实现必须被发布,同时也必须得到尊重。关于平凡可交换的事项,请注意任何可移动构造和可移动赋值的事物都可以针对自己的类型进行交换,即使它缺乏重载的交换解析挂钩,事实上有一些晦涩的技术可以禁用不需要的可交换行为。
考虑到这一点,也许所有的问题都可以归结为对std::iter_swap标识符本身的不正确解释:它并不代表“迭代器交换”,而是“可迭代对象交换”。不要被参数必须是前向迭代器的标准要求所迷惑:实际上,指针是随机访问迭代器,因此满足要求。物理上通过指针传递,逻辑上通过迭代器传递。委员会通常试图指定一个设施与定义和预期行为一起工作的最小要求,没有更多的要求。 “可迭代对象交换”名称恰当地展示了该机制的目标和功能。 "std::iter_swap"标识符似乎并没有产生混淆,但现在改变它并撤销所有依赖于它的代码库已经太晚了。
请随意交换,只要它能正常工作,但请不要在我的监视下这样做。混合抽象层不会使编译器出错,但接口太酷了,不能避免。相反,这里有一段代码片段,以帮助未来的指导:
#include <algorithm>
namespace linker {
class base {
};
class member {
};
template<class M = member, class B = base>
class link : B {
public:
void swap(link &other) {
std::iter_swap(static_cast<B*>(this), static_cast<B*>(&other));
std::iter_swap(&_member, &other._member);
}
private:
M _member;
};
template<class base, class member>
void swap(link<base,member>& left, link<base,member>& right) {
left.swap(right);
}
}
namespace processor {
template<class A, class B>
void process(A &a, B &b) {
std::iter_swap(&a, &b);
}
}
int main() {
#if !defined(PLEASE_NO_WEIRDNESS)
typedef
linker::link<
linker::link<
linker::link< int[1] >,
linker::link< void*, linker::link<> >
>[2],
linker::link<
linker::member[3]
>
>
swappable[4];
#else
typedef linker::link<> swappable;
#endif
swappable a, b;
processor::process(a, b);
}
一些额外的指导要点:
交换意味着抛出异常。这个语句听起来很愚蠢,但是一旦你知道交换惯用语并不关注性能而是极端的安全性和健壮性,它就不再愚蠢了。
std::iter_swap 展示了元编程中许多可爱但被忽视的特性之一:模板不仅可以进行重载决议,还可以进行命名空间决议,允许其作为未知和不相关命名空间链中的第一个使用。谢谢,少了一件事要担心。
可交换要求允许您直接使用 std::swap,如果(且仅当)您可以承担做出两个目标都是基本类型或基本类型 c 数组的假设,从而允许编译器绕过任何重载决议。不幸的是,这排除了几乎每个模板的参数。直接使用 std::swap 意味着两个目标是相同类型(或被强制成相同类型)。
不要浪费精力在声明对于已经可以轻松自我交换的类型(例如我们的链接模板类)的可交换能力上,尝试删除 linker::swap,行为不会改变。
“swap” 被设计成可扩展以从不同类型进行交换,对于相同类型则自动进行。请注意,一个类型本身并不是“可交换的”或“不可交换的”,而是“与另一个类型可交换”或“与另一个类型不可交换”。
最后,我想知道有多少读者会注意到
并认识到实用程序不是算法。在 Microsoft-Dinkumware 实现中,std::iter_swap 只是为了方便而存在于错误的头文件中,这并没有错。也许只是它的标识符有误。
编辑:在面对更多相关错误后,我想总结一下:算法是一个非常通用和具体的概念,每当有人要专门化其中之一时,设计师就会在别处哭泣。在 std::iter_swap 的情况下,由于委员会明确没有给予任何自由,任何试图像重新链接的猜测那样调整算法的尝试都应该有不同的含义和标识符。此外,也许有人错过了容器确实有一个 swap 成员函数,其中优化确实适用。
更好的重构方式是使您的最终层对象成为非数据、基本或表示隐藏较重对象(如果足够重,则进行流式处理)。接受资源获取应该是初始化(RAII),并且新的删除重载和容器分配器都有用途,以零额外工作释放真正的交换效益。优化资源,使您只在重新获取时移动数据,然后让 C++ 默认轻松、安全、快速地设计您的类型。
座右铭:在过去,人们苦于内存占用过大、磁盘读写速度过慢的数据问题。如今,迭代器向量从存储池中筛选出来,并通过并行管道进行流式处理。明天的汽车将会自动驾驶。值得一张便利贴。
iter_swap
永远不能重新链接链表的节点,因为这样一个持有链表迭代器的用户会看到使用swap(*a, *b)
交换内容,但不是使用iter_swap(a, b)
,这与iter_swap(a, b)
必须像swap(*a, *b)
一样行事的要求相矛盾。现在,这是否可取是完全不同的问题... - Marc Mutz - mmutzstd::iter_swap
,而不是为值或 r-value 参数重载人工std::swap
。 - alfCiter_swap
的可能有用性。 http://ericniebler.com/2015/02/03/iterators-plus-plus-part-1 - Predelnik