9

由于正确使用 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);
    }
}
4

3 回答 3

6

这是进入重言式的领域,但它不会那样做,因为这不是它的目的。

的目的std::swap是作为最后手段的交换功能。它应该是你直接调用的东西,除非你真正想要的是使用 swap-of-last-resort。

现在,您可以争辩说您的建议是定制点的更好范例。事后诸葛亮总是 20/20;并非 STL 所做的一切都是正确的想法(请参阅 参考资料vector<bool>)。

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

于 2017-11-20T00:10:35.410 回答
4

这是这种方法的一个问题。ADL“两步”依赖于 ADL 发现比正常匹配更好的函数(否则,您会遇到重载解析失败)。这在大多数情况下都很好,因为当您swap()在用户定义的命名空间中为您的用户定义类型编写您的类型时,您正在编写特定于这些类型的函数。

但是对于标准库类型,它可能没有swap()比简单算法更有效的算法,这会失败。例子:

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

出于同样的原因,这两个调用都失败了。swap()对标记的不合格调用(*)N::internal::swap()通过正常的不合格查找查找,然后N::swap()通过ADL查找。没有办法区分这些调用,因为您非常需要这两个调用来处理所有满足swap's 约束的类型。结果调用是模棱两可的。

所以这样的设计需要swap()为每个类型添加一个新函数namespace std,即使它只是转发到std::internal::swap()

于 2017-11-20T02:09:25.857 回答
3

从历史上看,似乎没有太多关于名称解析的想法。std::swap被设计为一个定制点,但也最终成为人们在通用代码中调用以交换事物的函数。因此,如果没有工作,或者速度太慢,那么即使ADLstd::swap已经有一个完美的优点,也可能不得不超载它。swap如果不破坏或更改现有代码的含义,就无法更改此设计。现在有一些情况下,委员会很高兴地决定为了性能而改变现有代码的含义,例如隐式移动语义(例如,当按值传递临时值时,或未实现省略的 RVO 时)。所以这并不完全一致。尽管如此,改变std::swap从定制点到名称解析包装器以及所有现有的std::swap重载都是可疑的。这绝对可能导致在编写不佳的遗留代码中触发灾难性错误。

另一个重要的原因是,IMO,你不能将这个习语移到std命名空间,同时保持它的通用性。例如:

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(via 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重载更好的匹配,这将使用交换函数,但algorithms::swap不会被 ADL 找到,因此查找通常不会发生在内部std::swap。这里可以涉及任意数量的命名空间。

于 2017-11-20T07:34:24.340 回答