如何重载std :: swap()

时间:2008-08-14 19:24:17

标签: c++ performance optimization stl c++-faq

在排序甚至分配期间,许多标准容器(例如std::swap()std::list)都使用

std::vector

但是swap()的std实现非常普遍,而且对于自定义类型来说效率很低。

因此,通过使用自定义类型特定实现重载std::swap()可以获得效率。但是如何实现它以便std容器使用它?

4 个答案:

答案 0 :(得分:123)

重载交换的正确方法是将其写入与交换相同的命名空间中,以便可以通过argument-dependent lookup (ADL)找到它。一件特别容易的事情是:

class X
{
    // ...
    friend void swap(X& a, X& b)
    {
        using std::swap; // bring in swap for built-in types

        swap(a.base1, b.base1);
        swap(a.base2, b.base2);
        // ...
        swap(a.member1, b.member1);
        swap(a.member2, b.member2);
        // ...
    }
};

答案 1 :(得分:65)

注意Mozza314

这是对通用std::algorithm调用std::swap的影响的模拟,并让用户在名称空间std中提供它们的交换。由于这是一项实验,因此此模拟使用namespace exp代替namespace std

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            exp::swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

namespace exp
{
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

对我来说,打印出来:

generic exp::swap

如果您的编译器打印出不同的内容,那么它就无法正确实现模板的“两阶段查找”。

如果您的编译器符合(对C ++ 98/03/11中的任何一个),那么它将给出我显示的相同输出。在这种情况下,确实会发生你所担心的事情。将swap放入命名空间stdexp)并没有阻止它发生。

戴夫和我都是委员会成员,并且已经在这个标准领域工作了十年(而且并不总是彼此一致)。但是这个问题已经解决了很长时间,我们都同意它是如何解决的。无视Dave在这方面的专家意见/答案,这是你自己的危险。

此问题在C ++ 98发布后曝光。从2001年开始戴夫和我开始work this area。这是现代的解决方案:

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

void swap(A&, A&)
{
    printf("swap(A, A)\n");
}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

输出是:

swap(A, A)

<强>更新

已经观察到:

namespace exp
{    
    template <>
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

作品!那么为什么不使用呢?

考虑您的A是类模板的情况:

// simulate user code which includes <algorithm>

template <class T>
struct A
{
};

namespace exp
{

    template <class T>
    void swap(A<T>&, A<T>&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A<int> a[2];
    exp::algorithm(a, a+2);
}

现在它不再起作用了。 : - (

所以你可以将swap放在命名空间std中并让它工作。但是,当您拥有模板时,您需要记住将swap放在A的命名空间中:A<T>。因为如果你将swap放在A的命名空间中,这两种情况都会起作用,只需要记住(并教导其他人)就可以更容易地实现这一点。

答案 2 :(得分:52)

您不允许(通过C ++标准)重载std :: swap,但是特别允许您将自己的类型的模板特化添加到std命名空间。 E.g。

namespace std
{
    template<>
    void swap(my_type& lhs, my_type& rhs)
    {
       // ... blah
    }
}

然后std容器(以及其他任何地方)的用法将选择你的专业而不是普通容器。

另请注意,提供swap的基类实现对于派生类型来说还不够好。例如。如果你有

class Base
{
    // ... stuff ...
}
class Derived : public Base
{
    // ... stuff ...
}

namespace std
{
    template<>
    void swap(Base& lha, Base& rhs)
    {
       // ...
    }
}

这适用于Base类,但是如果你尝试交换两个Derived对象,它将使用std中的泛型版本,因为模板化交换完全匹配(并且它避免了仅交换'base'部分的问题你的派生对象)。

注意:我已经更新了这个以从我的上一个答案中删除错误的位。 D'哦! (感谢puetzk和j_random_hacker指出来)

答案 3 :(得分:29)

虽然通常不应该向std :: namespace添加内容是正确的,但是特别允许为用户定义的类型添加模板特化。重载功能不是。这是一个微妙的区别: - )

  

17.4.3.1/1     对于C ++程序来说,添加声明或定义是未定义的     除非另有说明,否则使用命名空间std来命名std或命名空间     指定。程序可以为任何程序添加模板特化     标准库模板到命名空间std。这样的专业化     (完整或部分)标准库导致未定义     行为,除非声明取决于用户定义的名称     外部链接,除非模板专业化符合     原始模板的标准库要求。

std :: swap的特殊化看起来像:

namespace std
{
    template<>
    void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}

没有模板&lt;&gt;这将是一个过载,它是未定义的,而不是一个允许的特化。 @Wilka建议更改默认命名空间的方法可能与用户代码一起使用(由于Koenig查找更喜欢无命名空间版本)但是它不能保证,实际上并不是真的应该(STL实现应该完全使用-qualified std :: swap)。

有一个thread on comp.lang.c++.moderated 主题的讨论。其中大部分是关于部分专业化的(目前没有好办法)。