如何重载 std::swap()

发布于 2024-07-04 05:12:26 字数 247 浏览 7 评论 0原文

std::swap() 被许多 std 容器(例如 std::liststd::vector)在排序甚至排序过程中使用任务。

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

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

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

贵在坚持 2024-07-11 05:12:26

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

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

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

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

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

有一个comp.lang.c++.moderated< /a> 对主题进行讨论。 不过,其中大部分都是关于部分专业化(目前没有好的方法可以做到)。

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 :-)

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.

A specialization of std::swap would look like:

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).

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

心碎无痕… 2024-07-11 05:12:26

重载 std::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);
        // ...
    }
};

The right way to overload std::swap's implemention (aka specializing it), 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:

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);
        // ...
    }
};
心如荒岛 2024-07-11 05:12:26

注意 Mozza314

这是调用 std::swap 并让用户提供交换的通用 std::algorithm 效果的模拟在命名空间标准中。 由于这是一个实验,因此该模拟使用 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 放入命名空间 std (exp) 并不能阻止它的发生。

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

这个问题是在 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);
}

输出是:

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'当您有模板时,请使用 s 命名空间:A,并且如果您将 swap 放入 A' 中,这两种情况都会起作用。 s 命名空间,只是更容易记住(并教导其他人)以一种方式执行此操作。

Attention Mozza314

Here is a simulation of the effects of a generic std::algorithm calling std::swap, and having the user provide their swap in namespace std. As this is an experiment, this simulation uses namespace exp instead of 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 swap into namespace std (exp) did not stop it from happening.

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.

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:

// 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 A is a class template:

// 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 swap in namespace std and have it work. But you'll need to remember to put swap in A's namespace for the case when you have a template: A<T>. And since both cases will work if you put swap in A's namespace, it is just easier to remember (and to teach others) to just do it that one way.

是你 2024-07-11 05:12:26

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

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

,std 容器(以及其他任何地方)中的用法将选择您的专业而不是通用的。

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

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

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

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

注意:我已更新此内容以删除上一个答案中的错误位。 噢! (感谢 puetzk 和 j_random_hacker 指出)

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.

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.

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

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).

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)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文