与右值交换

时间:2013-09-22 09:42:43

标签: c++ swap move-semantics rvalue-reference

假设我希望swap适用于rvalues,并且不想为rvalue / lvalue引用的所有组合编写4个版本(rvalue / rvalue版本有点无意义但不会受到伤害)。我想出了这个:

template <typename A, typename B>
struct is_same_no_ref
    : std::is_same<
        typename std::remove_reference<A>::type,
        typename std::remove_reference<B>::type
    >
{};

template <typename A, typename B,
    typename = typename std::enable_if<is_same_no_ref<A, B>::value>::type
>
inline void my_swap(A&& a, B&& b) {
    typename std::remove_reference<A>::type t = std::move(a);
    a = std::move(b);
    b = std::move(t);
}

似乎按预期工作。这个可以吗?或者我错过了一些让我后来受苦的重要事情?

1 个答案:

答案 0 :(得分:6)

虽然我在实施中没有看到固有的概念缺陷,但我会提出三点建议。

注意:我将在这里引用交换rvalues 概念的实现为swap_rvalues


从模板扣除中排除const类型

  • 如何?

假设

template <typename A, typename B>
struct is_same_no_ref
    : std::is_same<
        typename std::remove_reference<A>::type,
        typename std::remove_reference<B>::type
    >
{};

启用条件std::enable_if<std::is_same_no_ref<A, B>::value>更改为以下内容。

std::enable_if<
    std::is_same_no_ref<A, B>::value &&
    !std::is_const<typename std::remove_reference<A>::type>::value &&
    !std::is_const<typename std::remove_reference<B>::type>::value
>
  • 为什么吗

不从模板推导中排除const类型,将const变量传递给swap_rvalues,如下所示,

int const a = 0, b = 0;
swap_rvalues(a, b);

诱导编译器标记有关内部实现细节的错误,这不是非常用户友好的。


启用条件移动到返回类型声明

  • 如何?

而不是

template<typename A, typename B, typename = typename std::enable_if<...>::type>
inline void swap_rvalues(A&& a, B&& b);

声明如下

template<typename A, typename B>
inline typename std::enable_if<...>::type swap_rvalues(A&& a, B&& b);
  • 为什么吗

即使高度不可能,也可以明确定义swap_rvalues的第三个模板参数,从而有效地覆盖启用条件。这可能允许编译不应该编写的代码,并且可能会产生恶意。使用启用条件的返回类型声明可以完全避免这种情况。

请考虑以下示例。

template <
        typename A, 
        typename B, 
        typename = typename std::enable_if<
            is_same_no_ref<A, B>::value &&
            !std::is_const<typename std::remove_reference<A>::type>::value &&
            !std::is_const<typename std::remove_reference<B>::type>::value
        >::type>
inline void
swap(A&& a, B&& b) {
    typename std::remove_reference<A>::type t = std::move(a);
    a = std::move(b);
    b = std::move(t);
}

struct B;
struct A{A(){} A(B const&){}};
struct B{B(){} B(A const&){}};
swap<A, B, void>(A(), B());

它编译,即使它显然不应该! AB甚至不相关,只是在给定另一个参考的情况下,它们恰好是可构造的。


重用代码[aka KISS]

  • 如何?

由于rvalues已经指定了名称,只需转发std::swap,而不是提供swap_rvalues的全新实施。

  • 为什么吗

为什么reinventing the wheel†?一旦rvalues 被赋予了名称std::swap已经提供了预期的行为,那么为什么不重用呢?


结论

swap_rvalues‡的最终实施如下所示。

template <typename A, typename B>
inline typename std::enable_if<
    is_same_no_ref<A, B>::value &&
    !std::is_const<typename std::remove_reference<A>::type>::value &&
    !std::is_const<typename std::remove_reference<B>::type>::value
>::type
swap_rvalues(A&& a, B&& b) {
    std::swap(a, b);
}

<强>脚注

†​​重新发明轮子是复制先前已由其他人创建或优化的基本方法。”

事实上,

swap_rvalues最好在真实场景中被称为swap