条款34:优先考虑lambda而非std::bind

1.很重要的原因是lambda表达式更加简洁易读。

实现一个函数,它返回其实参是否在最小值(low)和最大值(high)之间的结果,其中lowhigh是局部变量。

int low=10;
int high=345;
//C++14  使用lamdba
auto betweenL =[low, high](const auto& val){ return low <= val && val <= high;};


//使用std::bind实现相同效果
using namespace std::placeholders;              
//c++14
auto betweenB =
    std::bind(std::logical_and<>(),           
              std::bind(std::less_equal<>(), low, _1),
              std::bind(std::less_equal<>(), _1, high));

//c++11
auto betweenB =
    std::bind(std::logical_and<bool>(),        
              std::bind(std::less_equal<int>(), low, _1),
              std::bind(std::less_equal<int>(), _1, high));

可以看到,lambda版本不仅更短,而且更易于理解和维护。

2.lambda 可以指定值捕获和引用捕获,而std::bind总会按值拷贝实参,要按引用传递则需使用std::ref;std::bind生成的对象调用中,实参如何传递也是疑惑,难以从代码中看出来。

void f(const A&,int& val);
using namespace std::placeholders;
A a;

//lamdba
auto l=[a](int v){f(a,v);};    //a是按值捕获,v是按值传参
//调用ladmba
int c=23;
l(c);    //实参c是按值传参


//std::bind
auto b = std::bind(f, a, _1);
auto b2=std::bind(f,std::ref(a),_1);
//调用std::bind
b(c);        实参c是如何传递??

这里要解决两个疑惑:

1.std::bind中的a是按值捕获的还是按引用捕获的。

2.std::bind中的_1占位符是按值传参还是按引用传参的。

问题1,lamdba可以很容易地看出来是按值捕获的。而使用std::bind不好看出来a是按值捕获还是按引用捕获的。

答案是std::bind中a是按值捕获的。std::bind总是拷贝它的实参。

但是调用者也可以使用引用来存储实参,这要通过应用std::ref到实参上实现,b2就是按引用捕获的。

所以这样一对比,还是使用lamdba方便易懂。

问题2,lamdba中很容易看到实参c是按值传参的。因为lamdba表达式中小括号内是(int val),这是按值传参。

而std::bind中,占位符_1对std::bind的调用中没有任何迹象表明是按值传参还是按引用传参的。

唯一的方法是记住std::bind的工作方式。答案是传递给bind对象的所有实参都是通过引用传递的,因为此类对象的函数调用运算符使用完美转发

又一对比,使用lamdba真是方便易懂呀。

3.lambda 可以正常使用重载函数,而 std::bind 无法区分重载版本,为此必须指定对应的函数指针类型。lambda 闭包类的 operator() 采用的是能被编译器内联的常规的函数调用,而 std::bind 采用的是一般不会被内联的函数指针调用,这意味着 lambda 可能比 std::bind 运行得更快。

void f(int){}
void f(double){}

auto b1=[](){f(1);};
auto b2=[](){f(2.3);};

auto g = std::bind(f, 1);                              // error
auto g = std::bind(static_cast<void (*)(int)>(f), 1);  // OK

4.实参绑定的是 std::bind 返回的对象,而非内部的函数

void f(std::chrono::steady_clock::time_point t, int i) {
  std::this_thread::sleep_until(t);
  std::cout << i<<std::endl;
}

int main() {
  using namespace std::chrono_literals;
  using namespace std::placeholders;

  auto l = [](int i) { f(std::chrono::steady_clock::now() + 8s, i); };
  l(2);  // 8 秒后打印 2

  auto b = std::bind(f, std::chrono::steady_clock::now() + 8s, _1);
  b(2);  // 调用 std::bind 后的 8 秒打印 2,而非调用 f 后的 8 秒
}

lambda中,表达式steady_clock::now() + 8s显然是函数f的实参。调用函数f时将对其进行计算。可以理解:我们希望在调用函数f后8s响铃。

这个情况为什么使用 std::bind不能达到我们的要求的?

std::bind调用中,将steady_clock::now() + 8s作为实参传递给了std::bind,而不是函数f。这意味着将在调用std::bind时对表达式进行求值,并且该表达式产生的时间将存储在产生的bind对象中。结果,警报器将被设置为在调用std::bind后8s发出声音,而不是在调用f8s发出。

解决办法是延迟到调用绑定的函数时再计算表达式值,这可以通过在内部再嵌套一个 std::bind 来实现。复杂繁琐。

auto b = std::bind(f,
                   std::bind(std::plus<>{}, std::chrono::steady_clock::now(),8),
                   _1);

 5.在C++14中,没有std::bind的合理用例。

但是,在C++11中,可以在两个受约束的情况下证明使用std::bind是合理的:

  • 移动捕获。C++11的lambda不提供移动捕获,但是可以通过结合lambdastd::bind来模拟。 
  • 多态函数对象。因为bind对象上的函数调用运算符使用完美转发,所以它可以接受任何类型的实参。当你要绑定带有模板化函数调用运算符的对象时,此功能很有用。

最终结论:

综上所述,可以使用lamdba代替std::bind

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值