iter_swap()与swap() - 有什么区别?

时间:2012-12-24 17:42:14

标签: c++ swap

MSDN says

  

swap应优先于iter_swap使用,后者包含在C ++标准中以实现向后兼容。

但是comp.std.c++ says

  

大多数STL算法在迭代器范围上运行。因此它是有意义的   在这些范围内交换元素时使用iter_swap,因为那是它的   预期目的---交换两个迭代器指向的元素。这个   允许对基于节点的序列进行优化,例如std::list   节点只是重新链接,而不是实际交换的数据。

哪一个是正确的?我应该使用iter_swap,还是应该使用swap? (iter_swap仅用于向后兼容吗?)为什么?

6 个答案:

答案 0 :(得分:22)

标准本身很少提及iter_swap

  • 它应该具有swap(*a, *b)的效果,尽管没有规定它必须以这种方式实施。
  • 取消引用的值*a*b必须是“可交换的”,这意味着swap(*a, *b)必须有效,因此解除引用的类型必须相同,尽管迭代器类型不是必须是。
  • iter_swap需要用于std::reverse的实施。对任何其他算法都没有这样的要求,所以这似乎很奇怪。

what sehe had found 借用SGI docs

  

严格来说,iter_swap是多余的。它仅出于技术原因而存在:在某些情况下,某些编译器难以执行解释swap(*a, *b)所需的类型推导。

所有这些似乎都表明它是过去的神器。

答案 1 :(得分:16)

这似乎是互联网产生大量冲突信息的场景之一。

[C++11: 25.3.3/5]下的标准仅表示iter_swap(a,b)的结果为swap(*a,*b)(并且要求“ab可以取消引用”,并且“*a应该可以与*b”进行交换,这乍一看与MSDN的解释相关。

但是,我认为Microsoft忽略了考虑as-if规则,在某些情况下(例如链表的元素),这应该允许实现使iter_swapswap更快。 / p>

因此,我相信comp.std.c++报价在技术上更准确。

话虽如此,对可执行的优化存在相当严格的限制。例如,考虑iter_swap对链接列表元素的实现,它只是简单地重新链接节点而不是物理交换元素值 - 这是一个有效的实现,因为要求{ <1}}违反了iter_swap的可观察行为匹配。

因此,我建议在实践中优先考虑swap优于iter_swap ,几乎没有任何好处,我建议为了简单和一致,坚持后者。在许多情况下,C ++ 11移动语义应该使swap变得简单。

答案 2 :(得分:10)

是的,他们都做同样的事情,正确使用。不,std::iter_swap未被弃用(通过置于标准的§D兼容性功能部分)。 MSDN的引言误导性地不屑一顾。问题是正确使用std::swap是不方便的。

你应该使用iter_swap,原因很简单,因为它是一个更高的抽象。

对于用户定义的类型,

swap通常会重载。调用它的正确方法是

using std::swap;
swap( blah, bleh );

不仅仅是

std::swap( blah, bleh );

这在第17.6.3.2节中有所体现,特别是¶3:

  

评估swap(t, u)swap(u, t)的上下文应确保在候选集上通过重载解析(13.3)选择名为“swap”的二进制非成员函数,其中包括:

     

- swap(20.2)和

中定义的两个<utility>函数模板      

- 由参数依赖查找(3.4.2)生成的查找集。

iter_swap不是一个特殊的重载名称,自定义其功能需要向namespace std {}添加模板专精。


因此,iter_swap有用地封装了每次都可以实现的Swappable接口部分。

它实际上是一个更友好的界面,无论您的实现及其特定参数是否存在语义差异。 (并不是应该忽略对它的潜在优化.MSDN可能会发表意见,但他们无法预测使用“向后兼容性接口”可能提供的库作者。)


至于iter_swap的专业化与swap( *a, *b )的结果明显不同,这似乎与要求§25.3.3/ 5不符,

  

效果:swap(*a, *b)

您引用的示例听起来像是一个可观察的差异,因为指向*a*b的指针在操作之前和之后都有效。不幸的是,这是库实现中的一个错误。

答案 3 :(得分:5)

你已经达到了关键区别。

swap(*a, *b)是一个全局函数,它重置指向*a的所有指针,指向*b的内容,反之亦然。这是旧tmp = aa = bb = tmp交换。

iter_swap用于修改被迭代的底层对象以影响它们所属的结构的更有限的情况。如果*a*b属于同一个链接列表,iter_swap只需在列表中交换其位置就足够了。当您想要简单地对列表进行排序而不会使列表中的对象无效/更改外部指针时,这是一个优点。如果我有一个指向user对象的指针,我不在乎你是否对list userswap对象进行排序,我不想知道谁是“当前”用户要更改,所以列表排序最好不要使用{{1}}。

答案 4 :(得分:0)

你应该使用哪一个?取决于你使用它的是什么。因为交换只适用于对象,交换两个独立的整数或字符串或双精度。但iter_swap适用于数组和列表,您可以在其中交换两个不同列表中的数字,如cplusplus.com

所示

答案 5 :(得分:0)

仔细阅读法律:


20.2.2 swap [utility.swap]

  • 模板无效交换(T&amp; a,T&amp; b) noexcept(is_nothrow_move_constructible :: value&amp;&amp; is_nothrow_move_assignable ::值);

    2要求:类型T应为MoveConstructible和MoveAssignable。 (表20)和(表22)

    3效果:交换存储在两个位置的值。

  • 模板void swap(T(&amp; a)[N],T(&amp; b)[N]) noexcept(noexcept(swap(* a,* b)));

    4要求:对于范围内的所有i,a [i]应与b [i]交换 [0,N)。 (17.6.3.2)

    5效果:swap_ranges(a,a + N,b)

25.3.3 swap [alg.swap]

  • 模板无效 iter_swap(ForwardIterator1 a,ForwardIterator2 b);

    5效果:交换(* a,* b)。

    6要求:a和b应可解除引用。 * a应可交换 与* b。 (17.6.3.2)


因此,iter_swap需要交换存储在两个解除引用的位置或取消引用位置范围内的值,并且任何交换引用或位置本身的尝试都在抵抗一致性。这显然禁止推测优化是std :: iter_swap背后的原因。相反,正如Potatoswatter正确指出的那样,封装和抽象是其存在的主要原因。 std :: iter_swap和std :: swap各自属于不同的抽象层,std :: swap本身和任何名为&#34; swap&#34;的二进制非成员函数的方式相同。通过重载分辨率选择不同。

交换开发人员和设计人员角色以理解实现相同的结果并不意味着相同,就像在&#34;甚至从基本类型声明typedef只是编译器的噪声,它对读者来说不是噪音&# 34 ;.把它当作一个笑话,但我们可以说整个C ++只是一个不可挽回的工件包装C,因为它们在最低级别做同样的事情,依此类推,任何代码块通过包装器代表另一个的抽象。特别是当线条如std :: iter_swap那样薄时,&#34; swap&#34;和std :: swap。也许&#34;使用std :: swap&#34;只有少数字符,一旦编译就会消失,但意味着注入一个标识符并构建一个完整的重载解析机制。一遍又一遍地注射,反复建造,一遍又一遍地替换,一遍又一遍地丢弃。远非抽象,封装和回收的方法。

暴露上层的内部工作给出了维护失败的潜在可能性。在交换域中,使用std :: swap&#34;丢失(或弄乱)&#34;在深度元编程包含设计中,将静默等待模板函数内部,等待一个简单的可交换,基本或c数组类型来破坏构建,如果幸运的话,甚至StackOverflow(TM)通过无限递归。显然,必须发布可扩展机制的实现,但也必须遵守。关于简单的可交换,请注意任何moveconstructi和moveassignable都可以替换它自己的类型,即使它没有重载的交换解决方案钩子,并且确实存在禁用不需要的可交换行为的模糊技术。

考虑到这一点,也许所有这些都可以在对std :: iter_swap标识符本身的不正确解释中恢复:它不代表&#34;迭代器交换&#34;,但是&#34;可迭代交换&# 34 ;.不要被作为前向迭代器的参数的标准要求所欺骗:实质上,指针是随机访问迭代器,因此满足要求。 Phisically你通过指针传递,逻辑上你通过迭代器。委员会通常会尝试指定设施使用定义和预期行为的最低要求,仅此而已。 &#34;可迭代交换&#34;名称正确地暴露了机制的目标和权力。 &#34; std :: iter_swap&#34;标识符似乎不是由于产生的混淆,但是更改它并撤消依赖的所有代码库为时已晚。

只要有效,请随意交换,但请不要在我的手表上。混合抽象层不会让编译器哭泣,但接口太酷了,无法避免。相反,这是一个帮助指导未来的片段:

//#include <utility> // std::swap is not required here
#include <algorithm> // std::iter_swap is

namespace linker {

    class base {
    };

    class member {
    };

    template<class M = member, class B = base> // requires swappable base and member
    class link : B {
    public:
        void swap(link &other) { // using iterable swapping
            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) { // extending iterable swapping
        left.swap(right);
    }

}

namespace processor {

    template<class A, class B>
    void process(A &a, B &b) { // using iterable swapping
        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]; // just an array of n-ary hierarchies
#else
    typedef linker::link<> swappable;
#endif
    swappable a, b;
    processor::process(a, b);
}

作为附加指导的一些兴趣点:

  • 交换意味着抛出异常。声明似乎很愚蠢,但是一旦你知道交换习语不是专注于性能而是关注极端的安全性和稳健性。

  • std :: iter_swap展示了元编程的许多可爱但被忽视的功能之一:模板不仅可以执行重载解析,还可以解析命名空间,允许将其用作未知和无关命名空间链中的第一个。谢谢,有一点不用担心。

  • 可交换的要求允许你直接使用std :: swap if(和 只有在你能负担得出这两个目标的假设的情况下 基本类型或基本类型的c-array,从而允许 编译器绕过任何重载决议。可悲的是,这排除了 几乎每个模板的参数。使用std :: swap直接暗示 两个目标属于同一类型(或强制属于同一类型)。

  • 不要浪费精力在一个类型上声明可交换功能 就像我们的链接模板一样,已经可以轻松地自行更换了 class(尝试删除链接器:: swap,行为赢了彻底改变) “swap”被设计为可扩展以从不同类型交换,
    相同类型的自动。介意类型不是'#34;可交换&#34;或
    &#34;非可交换&#34;本身,但&#34;可交换 - &#34;或
    &#34;非可交换的,与&#34;另一种类型。

最后,我想知道有多少读者会注意到

  • 20.2.2 swap [utility.swap]

  • 25.3.3 swap [alg.swap]

并认识到实用程序不是算法。在Microsoft-Dinkumware实现中,为方便起见,std :: iter_swap只是生活在错误的标题中,这并不是错误的。也许只是它的标识符是。


编辑:在遇到一些更相关的错误之后,我可以像这样对它们进行理解:算法是一个如此通用和具体的概念,每当有人专门研究其中一个时,设计师就会在其他地方哭泣。在std :: iter_swap的情况下,由于commitee显然没有给予自由,因此任何尝试在重新链接推测中调整算法都应该具有不同的含义和标识符。也许有人错过了容器确实有一个交换成员函数,其中优化确实适用。

更好的重构,因此您的最终图层对象是非数据,基础或代表隐藏较重的对象(如果足够重,则流式传输)。拥抱资源请求应该是初始化(RAII)并且新删除重载和容器分配器都具有use,以便通过零添加努力释放真正的交换优势。优化资源,以便仅在读取数据上移动数据,然后让C ++默认设置简单,安全和快速的类型。

座右铭:在过去,人们一直在努力处理内存太胖,磁盘太慢的数据。如今,迭代器向量从存储池中过滤掉,并流式传输以在并行管道中进行处理。明天汽车将独自开车。值得一个帖子。

相关问题