首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何重载std::swap()

如何重载std::swap()
EN

Stack Overflow用户
提问于 2008-08-14 19:24:17
回答 4查看 34.5K关注 0票数 122

许多标准容器(如std::liststd::vector)在排序甚至赋值过程中都会使用std::swap()

但是swap()的标准实现是非常通用的,并且对于自定义类型来说效率相当低。

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

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2010-04-22 00:02:22

重载std::swap的实现(也称为专门化它)的正确方法是将它写在与您要交换的东西相同的名称空间中,这样就可以通过argument-dependent lookup (ADL)找到它。

代码语言:javascript
复制
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);
        // ...
    }
};
票数 144
EN

Stack Overflow用户

发布于 2011-12-09 07:52:13

注意: Mozza314

这里模拟了一个通用std::algorithm调用std::swap的效果,并让用户在名称空间std中提供他们的交换。由于这是一个实验,因此此模拟使用namespace exp而不是namespace std

代码语言:javascript
复制
// 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);
}

对我来说,这是打印出来的:

代码语言:javascript
复制
generic exp::swap

如果你的编译器打印出一些不同的东西,那么它就没有正确地实现模板的“两阶段查找”。

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

Dave和我都是委员会成员,并且已经在这个标准领域工作了十年(并不总是彼此达成一致)。但这个问题已经解决了很长一段时间,我们都同意如何解决这个问题。忽略Dave在这一领域的专家意见/答案,后果自负。

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

代码语言:javascript
复制
// 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);
}

输出为:

代码语言:javascript
复制
swap(A, A)

更新

有一项观察表明:

代码语言:javascript
复制
namespace exp
{    
    template <>
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

成功了!那么为什么不使用它呢?

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

代码语言:javascript
复制
// 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放入名称空间标准中并使其正常工作。但是当你有一个模板:A<T>的时候,你需要记住把swap放在A的命名空间中。而且因为如果你把swap放在A的命名空间中,这两种情况都可以工作,所以更容易记住(也更容易教别人)只用一种方式就可以了。

票数 77
EN

Stack Overflow用户

发布于 2008-08-14 19:46:32

根据C++标准,您不允许重载std::swap,但是明确允许您将自己类型的模板专门化添加到std名称空间。例如。

代码语言:javascript
复制
namespace std
{
    template<>
    void swap(my_type& lhs, my_type& rhs)
    {
       // ... blah
    }
}

那么std容器(和其他任何地方)中的用法将选择您的专门化,而不是通用的专门化。

还要注意的是,为您的派生类型提供交换的基类实现还不够好。例如,如果你有

代码语言:javascript
复制
class Base
{
    // ... stuff ...
}
class Derived : public Base
{
    // ... stuff ...
}

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

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

注意:我已经更新了这个,去掉了我上一个答案中的错误部分。哦!(感谢puetzk和j_random_hacker指出)

票数 53
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/11562

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档