【问题标题】:Why is std::swap not using swap idiom?为什么 std::swap 不使用交换习语?
【发布时间】:2018-05-03 03:46:12
【问题描述】:

因为正确使用 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);
    }
}

【问题讨论】:

  • 可能会破坏很多现有代码。名称冲突或行为改变。
  • 抱歉,我在这里遗漏了什么吗? std::internal_do_not_use_directly::swap; 应该是什么?
  • Neil Butterworth - 正如评论用很少的话说的:),它与当前的 std::swap 具有相同的实现;
  • @NeilButterworth 我相信他提议的是std::swap,而实际的std::swap 应该在std::internal_do_not_use_directly::swap 中实现
  • @NoSenseEtAl:这不是无限递归到底是怎么回事?还是重载决议冲突?毕竟,这两个函数具有相同的签名和名称;什么会阻止编译器调用自己?

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


【解决方案1】:

从历史上看,似乎没有太多关于名称解析的想法。 std::swap 被设计为一个自定义点,但也最终作为一个函数,人们可以在通用代码中调用以交换事物。因此,如果std::swap 不起作用,或者太慢,那么即使已经使用 ADL 找到了一个非常好的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(通过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 内进行查找。这里可以涉及任意数量的命名空间。

【讨论】:

  • “巴里提到的问题可以简单地通过......” 不,它不能。大量用户代码自定义名称swap,不能重命名自定义点。
  • 你说得对,ADL 找不到swap 函数。不过,应该有一种 SFINAE 方法来做到这一点。我要重新考虑这个问题。
【解决方案2】:

这是这种方法的一个问题。 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(),然后通过ADL找到N::swap()。没有办法区分这些调用,因为您非常需要这两个调用来处理满足swap 约束的所有类型。结果调用是模棱两可的。

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

【讨论】:

  • 如果N::swap 不是函数,而是函子会怎样?
  • 您的回答提出了我没有考虑的重点,并通过暴露代码中的错误来回答我的问题。但是,如果您有兴趣进一步追查:如果 constexpr 在这里有帮助吗?请看:godbolt.org/g/KxWvrZ
  • @NoSenseEtAl 无限递归?此外,您的特征需要使用T&amp; 进行检查,而不是T
  • @NicolBolas 这会破坏一些依赖于 std::swap 作为函数的代码
  • Tnx Barry,我已经更新了代码。 coliru.stacked-crooked.com/a/c9b5d7186889bcde 在简单的例子中似乎可以正常工作...关于为什么检测习语需要&我假设它只适用于值,但我想问题是对临时引用的绑定是不好的:)。
【解决方案3】:

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

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

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

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

【讨论】:

  • “std::swap 的目的是作为最后的交换函数。它不应该是你直接调用的东西”我真的不知道这个。我只是假设如果你想交换东西就打电话给我,我从来没有认为这是最后的手段。这是您的意见还是 Bjarne/Stepanov 所说的?
  • @NoSenseEtAl:这就是函数的作用:它通过三次移动对对象执行硬交换。您要创建的函数优先考虑用户定义的交换函数,如果不存在,则将原始版本作为最后的手段调用。因此,这就是 std::swapusing std::swap; 成语中的目的:如果所有其他人都没有成功,则作为最后的手段。
猜你喜欢
  • 2015-08-04
  • 1970-01-01
  • 2021-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-30
  • 1970-01-01
相关资源
最近更新 更多