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

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

How to overload std::swap()

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

但是swap()的std实现非常笼统,对于自定义类型而言效率很低。

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


重载交换的正确方法是将其写入与交换内容相同的名称空间中,以便可以通过依赖于参数的查找(ADL)找到它。一件特别容易做的事是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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中提供其交换的效果的模拟。由于这是一个实验,因此此模拟使用namespace exp而不是namespace std

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// simulate

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap
"
);
        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

struct A
{
};

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

}

// exercise simulation

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

对我来说,它打印出来:

1
generic exp::swap

如果您的编译器打印出不同的内容,则说明它没有正确实现模板的"两阶段查找"。

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

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

C ++ 98发布后,此问题暴露出来。从2001年Dave开始,我开始在这一领域工作。这是现代的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// simulate

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap
"
);
        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

struct A
{
};

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

// exercise simulation

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

输出为:

1
swap(A, A)

更新资料

观察到:

1
2
3
4
5
6
7
8
9
10
namespace exp
{    
    template <>
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)
"
);
    }

}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// simulate user code which includes

template <class T>
struct A
{
};

namespace exp
{

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

}

// exercise simulation

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

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

因此,您可以将swap放在命名空间std中并使其起作用。但是,对于具有模板A< T >的情况,您需要记住将swap放在A的命名空间中。而且,如果将swap放在A的命名空间中,这两种情况都适用,那么记住(并教别人)以这种方式进行操作会更容易。


(按C ++标准)不允许您重载std :: swap,但是特别允许您将自己类型的模板特化添加到std名称空间。例如。

1
2
3
4
5
6
7
8
namespace std
{
    template<>
    void swap(my_type& lhs, my_type& rhs)
    {
       // ... blah
    }
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base
{
    // ... stuff ...
}
class Derived : public Base
{
    // ... stuff ...
}

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

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

注意:我已经对此进行了更新,以从我的上一个答案中删除错误的位。天哪! (感谢puetzk和j_random_hacker指出来)


虽然通常不应该在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.

std :: swap的特殊化如下所示:

1
2
3
4
5
namespace std
{
    template<>
    void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}

没有template <>位,它将是一个未定义的重载,而不是允许的特殊化。 @Wilka的更改默认名称空间的建议方法可能与用户代码一起使用(由于Koenig查找更倾向于使用无名称空间的版本),但这并不能保证,实际上也不是必须的(STL实现应使用完整的名称)。合格的std :: swap)。

在comp.lang.c ++。moded上有一个主题很长的讨论主题。不过,大多数内容都与部分专业化有关(目前尚无好方法)。


推荐阅读