C++ 如何重载std::swap()

C++ 如何重载std::swap(),c++,performance,optimization,stl,c++-faq,C++,Performance,Optimization,Stl,C++ Faq,std::swap()被许多std容器(例如std::list和std::vector)在排序和均匀分配期间使用 但是swap()的std实现非常通用,对于自定义类型来说效率相当低 因此,可以通过使用自定义类型特定的实现重载std::swap()来获得效率。但是,如何实现它,以便它将被STD容器使用?< P>您不允许(由C++标准)重载STD::SWAP,但是您被特别地允许将您自己的类型的模板专长添加到STD命名空间。例如 namespace std { template<>

std::swap()
被许多std容器(例如
std::list
std::vector
)在排序和均匀分配期间使用

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

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

< P>您不允许(由C++标准)重载STD::SWAP,但是您被特别地允许将您自己的类型的模板专长添加到STD命名空间。例如

namespace std
{
    template<>
    void swap(my_type& lhs, my_type& rhs)
    {
       // ... blah
    }
}
名称空间std
{
模板
无效掉期(my_类型和lhs、my_类型和rhs)
{
//……废话
}
}
然后std容器(以及其他任何地方)中的用法将选择您的专业化,而不是一般的专业化

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

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

namespace std
{
    template<>
    void swap(Base& lha, Base& rhs)
    {
       // ...
    }
}
类基
{
//…东西。。。
}
派生类:公共基
{
//…东西。。。
}
名称空间标准
{
模板
无效掉期(基础和lha、基础和rhs)
{
// ...
}
}
这将适用于基类,但如果您尝试交换两个派生对象,它将使用std的通用版本,因为模板交换是完全匹配的(并且它避免了仅交换派生对象的“基本”部分的问题)


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

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

17.4.3.1/1 对于C++程序来说,添加声明或定义是未定义的。 除非另有说明,否则使用名称空间std或名称空间std 明确规定。程序可以为任何对象添加模板专门化 命名空间标准的标准库模板。这样的专门化 标准库的(全部或部分)导致未定义 行为,除非声明依赖于用户定义的 外部链接,除非模板专门化满足 原始模板的标准库要求

std::swap的专门化如下所示:

namespace std
{
    template<>
    void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}
名称空间std
{
模板
无效交换(myspace::mytype&a,myspace::mytype&b){…}
}
如果没有模板位,它将是一个未定义的重载,而不是一个允许的专门化@Wilka建议的更改默认名称空间的方法可能适用于用户代码(因为Koenig lookup更喜欢无名称空间的版本),但不能保证,事实上也不应该这样做(STL实现应该使用完全限定的std::swap)


关于这个话题有一个长的讨论。不过,大部分内容都是关于部分专门化的(目前还没有好的方法来实现)。

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

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);
        // ...
    }
};

注意Mozza314

下面是一个通用
std::algorithm
调用
std::swap
,并让用户在名称空间std中提供其交换的效果模拟。由于这是一个实验,此模拟使用
名称空间exp
而不是
名称空间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);
}
如果您的编译器打印出不同的内容,那么它没有正确地实现模板的“两阶段查找”

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

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

这一问题是在C++98出版后才被发现的。大约从2001年开始,戴夫和我开始。这就是现代解决方案:

// 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);
}
更新

有人指出:

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

}
现在它不再工作了-(


所以你可以把
swap
放在namespace std中并让它工作。但是当你有一个模板:
A
时,你需要记住把
swap
放在
A
的名称空间中,因为这两种情况都可以工作,所以更容易记住(并教导他人)只需这样做。

在C++2003中,它最多是未指定的。大多数实现确实使用ADL来查找交换,但没有强制要求,因此您不能指望它。您可以为特定的具体类型专门化std::swap,如OP所示;只是不要期望专门化会被使用,例如用于该类型的派生类。我会惊奇地发现,实现仍然没有使用ADL来找到正确的交换。这是委员会的一个老问题。如果您的实现没有使用ADL来找到交换,请提交一份错误报告。@Sascha:首先,我在名称空间范围内定义函数,因为这是对泛型代码唯一重要的定义。因为int et。al.没有/不能有成员函数,std::sort等必须使用自由函数;它们建立协议。其次,我不知道为什么你反对有两个实现,但如果你不能接受有一个非memb,大多数类注定会被无效地排序
swap(A, A)
namespace exp
{    
    template <>
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}
// 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);
}