使用rvalue进行交换

16

假设我想要一个可以在rvalue上工作的swap函数,而且不想为所有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);
}

看起来一切都按预期进行。这样可以吗?还是我缺少某些重要的东西,会在以后受到影响?


为什么不直接使用这个:template <typename A, typename B> void swap(A&& a, B&& b) { auto t = move(a); a = move(b); b = move(t); } - masoud
@MM。这个不太对称。我确实意识到只有在AB是“相互可移动”的情况下才能工作,但仍然如此。 - yuri kilochek
我相信你的意思是 is_same_no_ref 而不是 std::is_same_no_ref - brunocodutra
1个回答

9

虽然我认为你的实现本质上没有概念上的缺陷,但我有三点建议。

(注: 在这里,我将指代对 swaping 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());

即使明显不应该编译,它仍然会编译!A和B甚至不相关,只是在给出对方的引用后可以构造。

重复使用代码 [又名 KISS]

  • 如何实现?

由于rvalues已经拥有了“名称”,因此只需转发调用std::swap,而不是提供全新的swap_rvalues实现。

  • 为什么这样做?

为什么要重新造轮子†?一旦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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接