【问题标题】:Proper use of universal references正确使用通用参考
【发布时间】:2014-06-19 00:17:39
【问题描述】:

在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
}

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

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 的类型调用大函数时它的行为...

但是……即使这样可行,我真的没有其他选择吗? (我的意思是......这对眼睛来说并不容易)

【问题讨论】:

  • 您可以使用static_assert,如果这对您来说更方便。
  • 你可以使用宏来生成doThingsWithABC的所有8个版本... ducks

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


【解决方案1】:

完美转发主要用于当您不知道如何使用数据时,因为您正在编写“用户提供”数据的通用包装器。

在您上面描述的简单程序系统中,您要做的三件事将是具体的任务。

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

如果复制有意义,但移动速度更快,而且移动成本低(常见情况),则它们应按值获取参数,并在存储本地副本时将其移出。

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

如果该功能无法从移动中受益,请通过const&amp;

如果复制没有意义,则按右值引用(不是通用引用)或按值获取。

在能够move move 两者都很好的情况下,如果您考虑完美转发,则仍然很昂贵。如上所述,这通常只发生在包装由代码库的“用户”设置的函数时,因为通常move 要么真的很便宜,要么与复制一样昂贵。您必须处于move 效率的中间或不确定阶段,才能实现完美转发。

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

完美的转发会导致生成的代码膨胀、构建缓慢、实现泄漏和难以理解的代码。

【讨论】:

    【解决方案2】:

    使用static_asserts 代替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
    }
    

    【讨论】:

    • static_assert 确实更具可读性。但是,当有其他函数重载时,它将不起作用。在这种情况下,enable_if (SFINAE) 是唯一的选择。
    猜你喜欢
    • 2020-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多