为什么std :: swap不使用swap idiom?

时间:2017-11-19 23:49:57

标签: c++ argument-dependent-lookup template-function customization-point

正确使用std :: swap是:

using std::swap;
swap(a,b);

它有点冗长,但它确保如果a,b有更好的交换定义,它会被选中。

所以现在我的问题是为什么std::swap未使用此技术实现,因此用户代码只需要调用std::swap

这样的事情(忽略noexcept和简洁约束):

namespace std {
    namespace internal {
        template <class T>      // normal swap implementation
        void swap(T& a, T& b) { // not intended to be called directly
            T tmp = std::move(a);
            a = std::move(b);
            b = std::move(tmp);
        }
    }

    template <class T>
    void swap(T& a, T& b) {
        using internal::swap;
        swap(a,b);
    }
}

3 个答案:

答案 0 :(得分:6)

这已经进入了重言式领域,但它没有这样做,因为这不是它的目的。

std::swap的目的是成为最后的交换函数。它应该是你直接调用的东西,除非你真正想要的是使用最后一次交换。

现在,您可以争辩说您的建议是更好的自定义点范例。后见之明总是20/20;不是STL所做的一切都是正确的想法(见vector<bool>)。

至于为什么我们现在无法改变 ,这是另一回事。 std::swap是最后的交换函数。因此理论上人们可以调用它并且期望它绕过任何用户定义的交换代码。因此,以这种方式改变它会破坏他们的代码。

答案 1 :(得分:3)

Here's one problem with this approach. The ADL "two-step" relies on the function that ADL finds to be a better match than the normal one (otherwise, you'd get an overload resolution failure). This is fine most of the time, since when you write your swap() for your user-defined types in your user-defined namespaces, you're writing functions specific to those types.

But for standard library types, that may not have a more efficient swap() than the simple algorithm, this breaks down. Example:

namespace N {
    namespace internal {
        template <typename T>
        void swap(T&, T&); // normal swap impl
    }

    template <typename T>
    void swap(T& a, T& b) {
        using internal::swap;
        swap(a, b); // (*)
    }

    struct C { };
}

N::C c;
swap(c, c);    // error
N::swap(c, c); // error

Both of these calls fail, for the same reason. The unqualified call to swap() marked (*) will find N::internal::swap() through normal unqualified lookup, and then find N::swap() through ADL. There is no way to differentiate between these calls, since you very much need both calls to work for all types that meet swap's constraints. The resulting call is ambiguous.

So such a design would necessitate adding a new swap() function for every type in namespace std, even if it would just forward to std::internal::swap().

答案 2 :(得分:2)

从历史上看,似乎没有太多关于名称解析的想法。 std::swap被设计为一个自定义点,但也被称为通用代码中用于交换事物的函数。因此,如果std::swap不起作用,或者速度太慢,则可能不得不重载它,即使已经有一个非常好的swap可以找到ADL。如果不破坏或改变现有代码的含义,则无法更改此设计。现在有些情况下,委员会很乐意决定为了性能而改变现有代码的含义,例如隐式移动语义(例如,当按值传递临时值时,或者没有实现省略的RVO)。所以这并不完全一致。尽管如此,将std::swap从自定义点更改为名称解析包装器并且存在所有现有的std::swap重载仍然是可疑的。这绝对会导致在写得不好的遗留代码中触发灾难性的错误。

另一个重要原因是,IMO,你不能将这个成语移到std命名空间,同时保持其一般性。 E.g:

namespace A { struct X{}; }
namespace B {
   using std::swap;
   void swap(A::X&, A::X&);

   template<typename T>
   void reverse(T (&ts)[4])
   {
       swap(ts[0], ts[3]);
       swap(ts[1], ts[2]);
   }

   void silly(A::X (&xs)[4])
   {
       reverse(xs);
   }
}

此处silly使用B::swap结束。这样做的原因是std::swap(通过using)和B::swap都具有相同的优先级,但后者是更好的匹配。现在,你可能会认为这是一个愚蠢的例子,所以这是另一个不那么做作的人:

namespace types { /*...*/ }
namespace algorithms { /*including some swap implementations for types from above...*/ }

template<typename T>
void reverse(T (&ts)[4])
{
    using std::swap;
    using algorithms::swap;
    swap(ts[0], ts[3]);
    swap(ts[1], ts[2]);
}

这将使用algorithms中的交换函数,如果它比任何std::swap重载更好,但ADL将找不到algorithms::swap,因此查找不能在std::swap内进行{1}},一般来说。这里可以涉及任意数量的命名空间。