Skip to content

Latest commit

 

History

History
159 lines (110 loc) · 3.9 KB

File metadata and controls

159 lines (110 loc) · 3.9 KB

条款30:熟悉完美转发的失败情形

给定目标函数 f 和转发函数 fwd,当以某特定实参调用 f 会执行某操作,而用同一实参调用 fwd 会执行不同操作,则称完美转发失败:

f(expr);
fwd(expr);

有一些不能实施完美转发的实参:

大括号初始化器

假设 f 的声明如下:

void f(const std::vector<int>& v);

此时 f 可以通过编译,而 fwd 无法通过编译:

f({1, 2, 3});   // ok
fwd({1, 2, 3}); // error!

完美转发会在下面两个条件中的任何一个成立时失败:

  • 编译器无法为一个或多个 fwd 的形参推导出类型结果。
  • 编译器为一个或多个 fwd 形参推导出“错误的”类型结果。

上述 fwd 的调用中,问题在于未声明为 std::initializer_list 类型擦函数模板传递了大括号初始化器,这样的语境是 不推导语境,编译器会禁止在 fwd 的调用过程中从表达式 {1, 2, 3} 出发来推导类型。

有一个简单的解决方案:

auto il = {1, 2, 3};
fwd(il);    // ok

0NULL 用作空指针

对它们的推导结果是整型,而非指针类型,替换为 nullptr 可以解决这个问题。

仅有声明的整型 static const 成员变量

考虑下面的代码:

class Widget {
public:
    static const std;:size_t MinVals = 28;
    ...
};
...
std::vector<int> WidgetData;
widgetData.reserve(Widget::MinVals);

尽管 Widget::MinVals 并无定义,编译器绕过了它缺少定义的事实,将 28 塞到所有提到 MinVals 的地方。但是如果产生了对 MinVals 取址的需求,上面的代码虽然能通过编译,但是因为缺少定义,链接期会出错。

MinVals 调用 f 没问题,但是调用 fwd 会出现链接期错误:

f(Widget::MinVals);     // ok
fwd(Widget::MinVals);   // error!

标准规定,按引用传递 MinVals 时要求 MinVals 有定义。解决方案是添加定义:

const std::size_t Widget::MinVals;  // Widget.cpp

重载函数名字和模板名字

假设 f 定义如下:

void f(int (*pf)(int));

假设有重载函数 processVal

int processVal(int value);
int processVal(int value, int priority);

processVal 可以传递给 f, 却无法传递给 fwd

f(processVal);      // ok
fwd(processVal);    // error!    

原因是 fwd 不知道该选择哪个重载版本,而 f 可以根据形参类型 processVal 来选择哪种重载版本。

同样的问题会出现在使用模板函数的场合:

template<typename T>
T workOnVal(T param)
{...}

fwd(workOnVal); // error!

解决方案是手动指定需要转发的那个重载版本或实例:

using ProcessFuncType =
    int (*)(int);

ProcessFuncType processValPtr = processVal;

fwd(processValPtr); // ok   
fwd(static_cast<ProcessFuncPtr>(workOnVal)); // ok

位域

观察下面这个可以表示IPv4头部的模型:

struct Ipv4Header {
    std::unit32_t version: 4,
                  IHL: 4,
                  DSCP: 6,
                  ECN: 2,
                  totalLength: 16;
    ...
};

f 接收 std::size_t 类型形参,然后用 IPv4Header 对象:

void f(std::size_t sz);

IPv4Header h;
...
f(h.totalLength);   // ok

但是 fwd 无法转发:

fwd(h.totalLength);

C++标准规定:非 const 引用不得绑定到位域。可以传递位域的仅有按值传递和常量引用传递。在常量引用形参的情况下,标准要求此时引用实际绑定到存储在某种标准整型中的位域值的副本。常量引用不可能绑定到位域,它们绑定到的是“常规”对象,其中复制了位域的值。

你可以自己制作一个副本,并以该副本调用转发函数:

auto length = static_cast<std::unit16_t>(h.totalLength);
fwd(length);