正确使用通用参考

时间:2014-06-19 00:17:40

标签: c++ c++11 perfect-forwarding universal-reference forwarding-reference

在c ++ 11之前,我曾经写过这样的代码:

// Small functions
void doThingsWithA(const A& a)
{
    // do stuff
}

void doThingsWithB(const B& b)
{
    // do stuff
}

void doThingsWithC(const C& c)
{
    // do stuff
}

// Big function
void doThingsWithABC(const A& a, const B& b, const C& c)
{
    // do stuff
    doThingsWithA(a);
    doThingsWithB(b);
    doThingsWithC(c);
    // do stuff
}

但是现在,使用移动语义,允许我的函数将rvalue引用作为参数并添加这些重载可能会变得有趣(至少在某些情况下):

void doThingsWithA(A&& a);
void doThingsWithB(B&& b);
void doThingsWithC(C&& c);

从我收集的内容来看,如果我希望能够在我的大函数中调用那些重载,我需要使用完美的转发,这可能看起来像这样(它的可读性稍差,但我想它可以是好的,模板类型的命名约定很好):

template<typename TplA, typename TplB, typename TplC>
void doThingsWithABC(TplA&& a, TplB&& b, TplC&& c)
{
    // do stuff
    doThingsWithA(std::forward<TplA>(a));
    doThingsWithB(std::forward<TplB>(b));
    doThingsWithC(std::forward<TplC>(c));
    // do stuff
}

我的问题是:这并不意味着如果我的小函数有其他重载,那么可以使用不属于它的类型参数来调用大函数吗?

我认为这可能有助于防止这种情况:

template<typename TplA, typename TplB, typename TplC,
class = typename std::enable_if<std::is_same<A, std::decay<TplA>::type>::value>::type,
class = typename std::enable_if<std::is_same<B, std::decay<TplB>::type>::value>::type,
class = typename std::enable_if<std::is_same<C, std::decay<TplC>::type>::value>::type>
    doThingsWithABC(TplA&& a, TplB&& b, TplC&& c)
{
    // do stuff
    doThingsWithA(std::forward<TplA>(a));
    doThingsWithB(std::forward<TplB>(b));
    doThingsWithC(std::forward<TplC>(c));
    // do stuff
}

虽然我不确定它是否不是太严格,因为我不知道如果我试图用可隐式转换为A,B或C的类型调用大函数它的行为......

但是......即使假设这种方法有效,我真的没有其他选择吗? (我的意思是......眼睛不容易)

2 个答案:

答案 0 :(得分:7)

完美转发主要是因为您不知道如何使用数据,因为您正在编写用户提供的&#39;的通用包装。数据

在上面描述的一个简单的程序系统中,你做的三件事就是具体的任务。

这意味着你会知道他们是否会从拥有可移动的数据源中受益,如果他们必须复制它们是否有意义,并且移动是否便宜。

如果复制有意义,但移动速度更快,移动便宜(常见情况),则应按参数按值进行移动,并在存储本地副本时将其移出。

然后,此规则以递归方式应用于调用3个子函数的函数。

如果该功能无法从移动中受益,请按const&

如果副本没有意义,请使用右值引用(不是通用引用)或按值。

如果您考虑完美转发,那么能够move 并且 move的优势仍然很昂贵。如上所述,这通常仅在包装由“用户”设置的功能时发生。您的代码库,通常move要么真的很便宜,要么像副本一样昂贵。您必须处于move效率的中间或不确定阶段,才能完美转发。

完美转发还有其他用途,例如容器变异器,但它们更为深奥。例如,当您在C ++ 11样式基于范围的backwards循环中链接多个范围变换器时,我的for(:)范围变换器将完美地将传入范围转发到存储中,以使参考生命周期扩展正常工作

疯狂地完美转发会导致生成的代码膨胀,构建缓慢,实施漏洞以及难以理解的代码。

答案 1 :(得分:5)

使用static_assert代替enable_if。恕我直言,这个选项不仅更容易在眼睛上,而且更加用户友好。如果违反了参数类型,编译器将打印一个明确的错误消息,而对于enable_if对应物,它会抱怨没有找到匹配的函数。

template<typename TplA, typename TplB, typename TplC>
void doThingsWithABC(TplA&& a, TplB&& b, TplC&& c)
{
  static_assert(std::is_same<A, std::decay<TplA>::type>::value, "arg1 must be of type A");
  static_assert(std::is_same<B, std::decay<TplB>::type>::value, "arg2 must be of type B");
  static_assert(std::is_same<C, std::decay<TplC>::type>::value, "arg3 must be of type C");
    // do stuff
    doThingsWithA(std::forward<TplA>(a));
    doThingsWithB(std::forward<TplB>(b));
    doThingsWithC(std::forward<TplC>(c));
    // do stuff
}