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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11562/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 12:14:03  来源:igfitidea点击:

How to overload std::swap()

c++performanceoptimizationstlc++-faq

提问by Adam

std::swap()is used by many std containers (such as std::listand std::vector) during sorting and even assignment.

std::swap()在排序甚至赋值期间被许多 std 容器(例如std::liststd::vector)使用。

But the std implementation of swap()is very generalized and rather inefficient for custom types.

但是 的 std 实现swap()对于自定义类型非常通用且效率低下。

Thus efficiency can be gained by overloading std::swap()with a custom type specific implementation. But how can you implement it so it will be used by the std containers?

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

回答by Dave Abrahams

The right way to overload swap is to write it in the same namespace as what you're swapping, so that it can be found via argument-dependent lookup (ADL). One particularly easy thing to do is:

重载 swap 的正确方法是将其写入与您要交换的名称相同的命名空间中,以便可以通过参数依赖查找 (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);
        // ...
    }
};

回答by Howard Hinnant

Attention Mozza314

注意 Mozza314

Here is a simulation of the effects of a generic std::algorithmcalling std::swap, and having the user provide their swap in namespace std. As this is an experiment, this simulation uses namespace expinstead of namespace std.

这是对通用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);
}

For me this prints out:

对我来说,这会打印出来:

generic exp::swap

If your compiler prints out something different then it is not correctly implementing "two-phase lookup" for templates.

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

If your compiler is conforming (to any of C++98/03/11), then it will give the same output I show. And in that case exactly what you fear will happen, does happen. And putting your swapinto namespace std(exp) did not stop it from happening.

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

Dave and I are both committee members and have been working this area of the standard for a decade (and not always in agreement with each other). But this issue has been settled for a long time, and we both agree on how it has been settled. Disregard Dave's expert opinion/answer in this area at your own peril.

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

This issue came to light after C++98 was published. Starting about 2001 Dave and I began to work this area. And this is the modern solution:

这个问题在 C++98 发布后才曝光。大约从 2001 年开始,Dave 和我开始在这个领域工作。这是现代解决方案:

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

Output is:

输出是:

swap(A, A)

Update

更新

An observation has been made that:

已经观察到:

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

}

works! So why not use that?

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

Consider the case that your Ais a class template:

考虑你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);
}

Now it doesn't work again. :-(

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

So you could put swapin namespace std and have it work. But you'll need to remember to put swapin A's namespace for the case when you have a template: A<T>. And since both cases will work if you put swapin A's namespace, it is just easier to remember (and to teach others) to just do it that one way.

所以你可以放入swap命名空间 std 并让它工作。但是,你需要记住把swapA的命名空间的情况下,当你有一个模板:A<T>。而且因为如果你把这两种情况下将工作swapA的命名空间,它只是更容易记住(并教其他人)只是做的一个方法。

回答by Wilka

You're not allowed (by the C++ standard) to overload std::swap, however you are specifically allowed to add template specializations for your own types to the std namespace. E.g.

你不被允许(根据 C++ 标准)重载 std::swap,但是你被特别允许为你自己的类型添加模板特化到 std 命名空间。例如

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

then the usages in the std containers (and anywhere else) will pick your specialization instead of the general one.

那么 std 容器(和其他任何地方)中的用法将选择您的专业而不是一般专业。

Also note that providing a base class implementation of swap isn't good enough for your derived types. E.g. if you have

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

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

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

this will work for Base classes, but if you try to swap two Derived objects it will use the generic version from std because the templated swap is an exact match (and it avoids the problem of only swapping the 'base' parts of your derived objects).

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

NOTE: I've updated this to remove the wrong bits from my last answer. D'oh! (thanks puetzk and j_random_hacker for pointing it out)

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

回答by puetzk

While it's correct that one shouldn't generally add stuff to the std:: namespace, adding template specializations for user-defined types is specifically allowed. Overloading the functions is not. This is a subtle difference :-)

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

17.4.3.1/1 It is undefined for a C++ program to add declarations or definitions to namespace std or namespaces with namespace std unless otherwise specified. A program may add template specializations for any standard library template to namespace std. Such a specialization (complete or partial) of a standard library results in undefined behaviour unless the declaration depends on a user-defined name of external linkage and unless the template specialization meets the standard library requirements for the original template.

17.4.3.1/1 除非另有说明,否则 C++ 程序向命名空间 std 或带有命名空间 std 的命名空间添加声明或定义是未定义的。程序可以将任何标准库模板的模板特化添加到命名空间 std。除非声明依赖于用户定义的外部链接名称并且模板特化满足原始模板的标准库要求,否则标准库的这种特化(完整或部分)会导致未定义的行为。

A specialization of std::swap would look like:

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

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

Without the template<> bit it would be an overload, which is undefined, rather than a specialization, which is permitted. @Wilka's suggest approach of changing the default namespace may work with user code (due to Koenig lookup preferring the namespace-less version) but it's not guaranteed to, and in fact isn't really supposed to (the STL implementation ought to use the fully-qualified std::swap).

如果没有 template<> 位,它将是未定义的重载,而不是允许的特化。@Wilka 建议的更改默认命名空间的方法可能适用于用户代码(由于 Koenig 查找更喜欢无命名空间版本),但不能保证,实际上也不应该这样做(STL 实现应该使用完全-合格的 std::swap)。

There is a thread on comp.lang.c++.moderatedwith a longdicussion of the topic. Most of it is about partial specialization, though (which there's currently no good way to do).

在 comp.lang.c++.moderated 上有一个线程,对该主题进行了长时间的讨论。不过,其中大部分是关于部分专业化的(目前还没有好的方法)。