玩命加载中 . . .

34-考虑lambda而非bind


考虑以下C++14的lambda使用,它返回其实参是否在最小值(lowVal)和最大值(highVal)之间的结果,其中lowValhighVal是局部变量

auto betweenL =
    [lowVal, highVal](const auto& val)    		//C++14
    { return lowVal <= val && val <= highVal; };

使用std::bind可以表达相同的内容,但是该构造是一个通过晦涩难懂的代码来保证工作安全性的示例:

using namespace std::placeholders;              //同上
auto betweenB =
    std::bind(std::logical_and<>(),             //C++14
              std::bind(std::less_equal<>(), lowVal, _1),
              std::bind(std::less_equal<>(), _1, highVal));

在C++11中,我们必须指定要比较的类型,然后std::bind调用将如下所示:

auto betweenB =
    std::bind(std::logical_and<bool>(),         //C++11版本
              std::bind(std::less_equal<int>(), lowVal, _1),
              std::bind(std::less_equal<int>(), _1, highVal));

当然,在C++11中,lambda也不能采用auto形参,因此它也必须指定一个类型:

auto betweenL =                                 //C++11版本
    [lowVal, highVal] (int val)
    { return lowVal <= val && val <= highVal; };

lambda版本不仅更短,而且更易于理解和维护

假设我们有一个函数可以创建Widget的压缩副本

enum class CompLevel { Low, Normal, High }; //压缩等级

Widget compress(const Widget& w,            //制作w的压缩副本
                CompLevel lev);

并且我们想创建一个函数对象,该函数对象允许我们指定Widget w的压缩级别。这种使用std::bind的话将创建一个这样的对象:

Widget w;
auto compressRateB = std::bind(compress, w, _1);

std::bind总是拷贝它的实参,但是调用者可以使用引用来存储实参,这要通过应用std::ref到实参上实现

auto compressRateB = std::bind(compress, std::ref(w), _1);

结果就是compressRateB行为像是持有w的引用而非副本

然而在lambda方法中,其中w是通过值还是通过引用捕获是显式的:

auto compressRateL =                //w是按值捕获,lev是按值传递
    [w](CompLevel lev)
    { return compress(w, lev); };

同样明确的是形参是如何传递给lambda的。在这里,很明显形参lev是通过值传递的

compressRateL(CompLevel::High);     //实参按值传递

但是在对由std::bind生成的对象调用中

compressRateB(CompLevel::High);     //实参如何传递?

答案是传递给bind对象的所有实参都是通过引用传递的,因为此类对象的函数调用运算符使用完美转发

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

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

例如

class PolyWidget {
public:
    template<typename T>
    void operator()(const T& param);
};

std::bind可以如下绑定一个PolyWidget对象

PolyWidget pw;
auto boundPW = std::bind(pw, _1);

boundPW可以接受任意类型的对象了

boundPW(1930);              //传int给PolyWidget::operator()
boundPW(nullptr);           //传nullptr给PolyWidget::operator()
boundPW("Rosebud"); 		//传字面值给PolyWidget::operator()

这一点无法使用C++11的lambda做到。但是,在C++14中,可以通过带有auto形参的lambda轻松实现

auto boundPW = [pw](const auto& param)  //C++14 
               { pw(param); };

请记住:

  • 与使用std::bind相比,lambda更易读,更具表达力并且可能更高效
  • 只有在C++11中,std::bind可能对实现移动捕获或绑定带有模板化函数调用运算符的对象时会很有用

文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录