Item 30:Familiarize yourself with perfect forwarding failure cases

Item 30:Familiarize yourself with perfect forwarding failure cases

完美转发不仅仅是转发对象,而且还转发对象的修饰,无论是左值右值还是const或者volatile。但是完美转发也有失效的时候。

情况1:
template<typename T>
void fwd(T&& param)
{
	f(std::forward<T>(param));
}

f(expression)
fwd(expression)

如果在上述模板函数中,ffwd做的不是同一件事,那么fwd将无法将expression成功完美转发给f
假如f声明为:

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

当调用时

f({1,2,3});

{1,2,3} 将会转换成 std::vector 格式。
但是当调用fwd 时:

fwd({1,2,3});

将会报错。原因是编译器会根据f的参数类型来进行推导,但是对于fwd的参数类型是不可知的。所以完美转发会在这些情况下失效:
1、编译器无法推导fwd参数的类型。
2、编译器推导出错误的fwd参数类型。这里的错误可能fwd的实例无法通过编译器推导的参数类型进行编译,也可能是fwd参数推导出来的类型与f接受的参数类型不一致。例如当f是重载函数的时候,推导出来的参数类型可能会导致f调用错误的函数。

解决这个问题可以用这个办法,利用auto的推导特性

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

将{1,2,3} 推导为 std::initializer_list 类型,然后进行转发。

情况2:

0或者NULL做空指针,解决这个很容易,就是用nullptr替代0或者NULL

情况3:

只声明未定义的static constconstexpr 成员。
一般情况下,当使用以上两种成员的时候,只声明,不定义,编译器会直接将其转换为整数值。不需要安排一块内存。

class Widget{
public: 
	static constexpr std::size_t MinVals = 28; //声明
};
std::vector<int> widgetData;
widgetData.reserve(Widget::MinVals); 

void f(std::size_t val);//假设定义f如此

f(Widget::MinVals); //这么调用没问题,f接受28为参数
fwd(Widget::MinVals);// 在link阶段会报错

因为fwd 的参数是一个综合引用,引用一般可以看做指针,所以当传入参数的时候,需要参数地址,即参数被定义。要想解决这个问题,我们需要对成员进行一个定义:

constexpr std::size_t Widget::MinVals;  // 在Widget的cpp文件中。

注意一点,这里就不用再进行初始化了,因为头文件中做过了,重复的话编译器会报错。

情况4:

重载和模板函数的名字,如下例子:

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

假设我们有两个重载函数:

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

当我们将重载函数传入给函数f的时候:

f(processVal);

这样做没问题,因为编译器知道应该将哪个函数传给f,根据此函数的接受的参数的限制。
但是:

fwd(processVal); // error! which processVal?

就会报错,因为作为模板函数,fwd并不知道应该将其推导为哪个函数。
同理:

template <typename T>
T workOnVal(T param) 
{...}
fwd(workOnVal); // error! which workOnVal instantiation?

也会有类似的问题。
解决方案,将传入函数进行初始化,将其初始化为fwd函数所能接受的类型:

using ProcessFuncType = int (*)(int);
ProcessFuncType processValPtr = processVal;
fwd(processValPtr); //方法一
fwd(static_cast<ProcessFuncType>(workOnVal)); //方法二
情况5:

位域。
上例子:

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

void f(std::size_t sz); //被调用的函数

IPv4Header h;
...
f(h.totalLength);   // fine 
fwd(h.totalLength); // error!

fwd的参数是一个const 的引用,而h.totalLength是一个non-const的位域,c++的标准禁止将non-const的引用绑定在位域上。原因是 位域可能由一段计算机字节的随机部分组成(例如 一个32-bit int的3-5bits),但是没有办法去指向其地址。最小的能用指针指向的单位是 char 类型
如何解决呢,复制后传入函数:

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

这一章有点难理解,尤其是英文的读物,并且最近确实有点懒,更新的有点拖沓,努力克服自己的懒惰,加油!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值