lambda表达式的高阶用法

2022-12-04 16:08:30 浏览数 (2)

Part1第6章 lambda表达式

//lambda表达式使得STL中的 ”_f“簇算法 std::find_if, std::remove_if,std::count_if

//比较函数的算法簇:std::sort, std::nth_element, std::lower_bound 出现的更加频繁和方便

//应用提醒:

/**

* @brief

*

* 1, lambda是源代码的组成部分

*

* std::find_if(container.begin(),container.end(), [](int val){

* retrun 0 < val && val < 10;});

*

* 2,lambda可以创建闭包,闭包会持有数据的引用或副本,1 中第三个实参在运行期传递给 std::find_id的对象

*

* 3,lambda都会触发编译器生成一个独一无二的闭包类,而闭包中的语句会变成它的闭包类成员函数的可执行指令

*

*/

代码语言:javascript复制
//闭包可以复制,可以有多个
    //x是局部变量
    int x;
    //c1是 lambda产生的闭包的副本
    auto c1 = [x](int y){
        return x*y > 55;
    };
    //c2是c1的副本
    auto c2 = c1;
    //c3是c2的副本
    auto c3 = c2;
    //c1,c2,c3都是同一 lambda产生的闭包的副本

1条款31:避免默认捕获模式

//避免默认捕获模式

//C 11中两种默认捕获模式:按引用或按值

//按引用的默认捕获可能导致空悬引用:会导致闭包包含指涉到局部变量的引用,或者指涉到 定义 lambda的作用域内的形参的引用,一旦由 lambda 所创建的闭包越过了该局部变量或形参的生命周期,那么闭包内的引用就会空悬

//情况1:

//定义一个元素为筛选函数的容量,其中每个筛选函数都接受一个 int,并返回一个 bool 以表示传入的值是否满足筛选条件

代码语言:javascript复制
//情况1:
//定义一个元素为筛选函数的容量,其中每个筛选函数都接受一个 int,并返回一个 bool 以表示传入的值是否满足筛选条件
using FilterContainer = std::vector<std::function<bool(int)>>;
//元素为筛选函数的容器
FilterContainer filters;
//如下筛选 divisor 倍数的函数
int copmuteDivisor(int a,int b)
{
    return a/b;
}
bool fun1(int data)
{
    return 0;
}
bool fun2(int data)
{
    return 1;
}
void addDivisorFilter()
{
    auto divisor = copmuteDivisor(10,2);
    filters.emplace_back([&](int value){
        return value % divisor == 0;//危险,对divisor的指涉可能空悬
    });
    /**
     * @brief 
     * 随时可能出错!
     * lambda是值涉到局部变量divisor的引用,但该变量在addDivisorFilter返回时就不存在了。
     * 该变量的销毁就是紧随 filters.empplace_back返回的那一时刻,也就是,添加到帅选器聚集
     * 的那个函数刚刚被添加就消亡了。
     * 
     * 使用这个筛选器,从它被创建的那一刻起,就会产生未定义的行为
     * 
     */

    //显式方式按引用捕获 divisor: 确实比较容易看出 lambda的生成依赖 divisor的生命期
    filters.emplace_back([&divisor](int value){
        return value % divisor == 0;//危险,对divisor的指涉可能空悬
    });

    //按值捕获:按值捕获一个指针以后,在 lambda创建的闭包中持有的是这个指针的副本,但你并没有办法
    //阻止 lambda之外的代码去针对该指针实施 delete操作所导致副本空悬
    filters.emplace_back([=](int value){
        return value % divisor == 0;
    });//现在 divisor不会空悬 输出 1
}

//情况2:

//闭包会被立即使用,例如,传给 stl算法,并且不会被复制,那么引用比它持有的局部变量或形参生命周期

//更长,就不才能在风险。真的是是这样吗?

//std::all_of 返回某作用域内的元素都满足某条件的判断

代码语言:javascript复制
//std::all_of 返回某作用域内的元素都满足某条件的判断
template<typename C>
void workWIthContainer(const C& container)
{
    auto divisor = copmuteDivisor(10,2);
    //为实现泛型算法,取得容器中的元素型别
    using ContElemT = typename C::value_type;

    using std::begin;
    using std::end;

    //如果所有容器中的元素值都是 divisor的倍数
    if(std::all_of(begin(container), end(container), [&](
        const ContElemT& value){
            return value % divisor == 0;
        }))
    {
        //若全是
        std::cout<<"all match"<<std::endl;
    }
    else
    {
        //若至少有一个不是
        std::cout<<"not all match"<<std::endl;
    }

    /**
     * @brief 
     * 看着安全!
     * 如果发现该 lambda在其他语境中有用,例如,加入到filters容器中成为一个函数元素,然后被复制并粘贴
     * 到其他闭包 比 divisor生命周期更长的语境中的话,你就又被拖回空悬的困境了。
     * 
     * 显式列出 lambda所依赖的局部变量或形参是更好的软件工程实践
     * 
     * C  14 中 auto 的形参可以是 auto
     */
}

//情况3:

//按值捕获:假设 Widget可以实施的一个操作是向筛选器中添加条目

代码语言:javascript复制
//按值捕获:假设 Widget可以实施的一个操作是向筛选器中添加条目
class Widget{
    public:
        void addFilter() const;
        void addFilter1() const;

    //private:
        int divisor;
};
void Widget::addFilter() const
{   
    std::cout<<"divisor: "<<divisor<<std::endl;
    
    filters.emplace_back([=](int value){
        return value % divisor == 0;});//错误,没有可捕获的divisor
    
    /**
     * @brief 
     * 以上实现对吗?
     * 捕获只能针对在创建 lambda的作用域内可见的非静态局部变量,包括形参
     * 在 Widget::addFilter的函数体欸,divisor并非局部变量,而是 Widget类的成员变量,压根无法捕获
     * 
     * 如果默认捕获被消除,代码就不会通过编译
     * 
     * 1, 如果是 [=] 没有问题
     * 2,如果使 []  无法编译, this
     * 3, 如果是 [divisor] 无法编译, divisor即不是局部变量也不是形参
     * 
     * 2,3是为什么呢?
     * 
     * 每一个非静态成员函数都持有一个 this指针,然后每当提及该类的成员变量时都会用到这个指针
     * 被捕获的实际上是 Widget的 this指针,而不是divisor,因此上述代码相当于:
     *
     */
    auto curPtr = this;
    filters.emplace_back([curPtr](int value){
        return value % curPtr->divisor == 0;
    });

    /**
     * @brief 
     * 因此,lambda闭包的存活与它含有其 this 指针副本的 Widget对象的生命周期绑定在一起
     * 
     */
}

//情况4:

//引入智能指针

代码语言:javascript复制
//引入智能指针
void doSomeWork()
{
    auto pw = std::make_unique<Widget>();
    pw->divisor = 5;
    pw->addFilter1();
    /**
     * @brief 
     * 有什么问题???
     *   pw->addFilter();
     * 
     * 一个含有指向 Widget指针(Widhet的this指针的副本)的筛选函数
     * 该函数被添加到filters中,不过当 doSomeWork执行结束之后,Widget对象即被管理着它的生命周期
     * 的 std::unique_ptr销毁,这一刻起,filters中就含有一个带有空悬指针的元素
     * 
     * 如何解决这个问题呢?
     * 
     * 可以通过将你想捕获的成员变量复制到局部变量中,而后捕获该局部变量的副本加以解决
     * auto divisorCopy = divisor;
     * 
     */
}
void Widget::addFilter1() const
{   
    auto divisorCopy = divisor;

    std::cout<<"divisor: "<<divisorCopy<<std::endl;

    filters.emplace_back([divisorCopy](int value){
        return value % divisorCopy == 0;});
    
    //按值得默认捕获:但是冒险呀,这才是最开始造成意外捕获this指针,而不是期望中得 divisor得始作俑者
    filters.emplace_back([=](int value){
        return value % divisorCopy == 0;});
    
    //C  14中:捕获成员变量的一种更好的方法是使用广义 lambda捕获
    filters.emplace_back([divisor = divisor](int value){
        return value % divisor == 0;});
}

/情况5:

//按值捕获的缺点:因为 lambda可能不仅依赖于局部变量和形参,他们可以被捕获,还会依赖静态存储期对象

//这样的对象在全局或名字空间作用域中,又或在类中,在函数中,在文件中以 static加以声明

//这样的对象在 lambda内使用,但是他们不能被捕获

//但是使用了默认值捕获模式,会给人一种错觉,认为他们可以被捕获

代码语言:javascript复制
void addDivisorFilter1()//运行结果怀疑怀疑了下面说法,明明可以呀???
{   
    //用 static声明
    static auto calc1 = 10;
    static auto calc2 = 2;

    static auto divisor = copmuteDivisor(calc1,calc2);

    std::cout<<"static 1: "<<divisor<<std::endl;
    filters.emplace_back([=](int value){
        std::cout<<"static 2: "<<divisor<<std::endl;
        return value % divisor == 0;//没捕获任何东西,指涉到前述以 static声明的对象
    });

    //意外修改了 divisor
      divisor;

    /**
     * @brief 
     * 给人的错觉, [=] 复制了它内部使用的对象,错了!
     * 
     * lambda没有使用任何的非静态局部变量和形参,没能捕获任何东西,更糟糕的是,指涉了静态变量 divisor
     * 因为每次调用 addDivisorFilter1的最后 divisor都会被递增,从而把好多lambda添加到 filters时每个lambda的行为不一样
     * 对应于 divisor的薪值,从实际效果来看是按引用捕获 divisor
     * 
     * 因此,从一开始就远离按值得默认捕获模式,就消除代码如此被误读了
     * 
     */
}

客户端测试:

代码语言:javascript复制
int main()
{
    //测试1;
    addDivisorFilter();
    std::function<bool(int)> f1 = fun1;
    std::function<bool(int)> f2 = fun2;
    
    for(auto i:filters)
    {
        std::cout<<"测试1: "<<i(5)<<std::endl;
    }

    //测试2:
    std::vector<int> vi = {100,50,1};
    workWIthContainer(vi);

    //测试3:
    Widget w;
    w.divisor = 5;
    w.addFilter();

    for(auto i:filters)
    {
        std::cout<<"测试3: "<<i(80)<<std::endl;
    }

    //测试4:
    doSomeWork();
    for(auto i:filters)
    {
        std::cout<<"测试4: "<<i(80)<<std::endl;
        //Floating point exception说明可能存在或者%的除数为0的情况
    }

    //测试5:
    addDivisorFilter1();
    addDivisorFilter1();
    addDivisorFilter1();
    addDivisorFilter1();
   // addDivisorFilter1();
    for(auto i:filters)
    {
        std::cout<<"测试5: "<<i(80)<<std::endl;
    }


}

测试1:0 测试1:0 测试1:1 not all match divisor: 5 测试3:0 测试3:0 测试3:1 测试3:1 测试3:1 divisor: 5 测试4:0 测试4:0 测试4:1 测试4:1 测试4:1 测试4:1 测试4:1 测试4:1 static 1: 5 static 1: 6 static 1: 7 static 1: 8 测试5:0 测试5:0 测试5:1 测试5:1 测试5:1 测试5:1 测试5:1 测试5:1 测试5:static 2: 9 0 测试5:static 2: 9 0 测试5:static 2: 9 0 测试5:static 2: 9 0

// • 按引用的默认捕荻会导致空悬指针问题

// • 按值的默认捕荻极易受空悬指针影响(尤其是 this) ,并会误导人们认为

// lambda 式是自洽的

2条款32:使用初始化捕获将对象移入闭包

/**

* @brief

* C 11

* 当你有个对象,移动操作比复制低廉,把一个只移对象 std::unique或std::future型别得对象放入闭包,C 并未提供任何办法

* 大部分标准库容器都是这样,但可以近似达到

*

* C 14

* 它为对象移入闭包提供了直接支持,初始化捕获,得到:

* 1,由 lambda生成得闭包类中得成员变量得名字

* 2,一个表达式,用以初始化该成员变量

*/

//情况1:c 14

//使用初始化捕获将 std::unique_ptr移动到闭包内

代码语言:javascript复制
//使用初始化捕获将 std::unique_ptr移动到闭包内
class Widget{
    public:
        bool isValidated() const{return true;}
        bool isProcessed() const{}
        bool isArchived() const{return true;}
    private:

};
//创建Widget,配置 *pw
auto pw = std::make_unique<Widget>();
//采用 std::move(pw) 初始化包类得数据成员
auto func = [pw = std::move(pw)](bool data){
    return pw->isValidated() && pw->isArchived() && data;};
    /**
     * @brief 
     * [pw = std::move(pw)]: 这就是初始化捕获
     * 左侧是你所指定的闭包类成员变量的名字,右侧是初始化表达式,两者处于不同的作用域
     * 左侧作用域就是闭包类的作用域,右侧得作用域则与 lambda定义得作用域相同
     * 含义是:
     * 在闭包中创建一个成员变量pw,然后使用针对局部变量 pw实施std::move得结果来初始化该成员变量
     * 
     * 如果auto pw 没被修改,没必要单独写出来,可以这样
     * 
     */
 auto func1 = [pw = std::make_unique<Widget>()](bool data){
    return pw->isValidated() && pw->isArchived() && data;
};

//情况2:c 11

//c 11不可能捕获一个表达式得结果,如何改写以上代码呢?

//lambda只不过是 生成一个类并且创建一个该类对象得手法罢了,因此并不存在手工做不到得事情

代码语言:javascript复制
//lambda只不过是 生成一个类并且创建一个该类对象得手法罢了,因此并不存在手工做不到得事情
class IsValAndArch{
    public:
        using DataType = std::unique_ptr<Widget>;
        explicit IsValAndArch(DataType&& ptr):pw(std::move(ptr)){}

        bool operator()()const{
            return pw->isValidated() && pw->isArchived();
        }
    private:
        DataType pw;//支持对成员变量实施移动初始化类
};

//情况3:bind c 11

//如果你非要用 c 11 模拟已上传操作,你需要

//1, 把需要捕获得对象移动到 std::bind 产生得函数对象中

//2,给到 lambda一个指涉欲 捕获得对象得引用

//先举一个简单的例子:

//创建一个局部变量 std::vector对象, 向其放入合适得一组值,然后移入闭包

//c 14

代码语言:javascript复制
//创建一个局部变量 std::vector对象, 向其放入合适得一组值,然后移入闭包
//c  14
std::vector<double> data = {2,3,4};
auto func2= [data = std::move(data)](){
    for (auto i: data){
        std::cout<<i<<std::endl;
    }
};
//c  11 bind
auto func3 = std::bind(
    [](const std::vector<double>& data) {
        
        std::cout<<"测试3-3: "<<data.size()<<std::endl;
        
        for (auto i: data){
            std::cout<<i<<std::endl;
    }},

    std::move(data)

    /**
     * @brief 
     * 
     * std::bind同lambda一样,也生存函数对象,返回得函数对象为绑定对象
     * std::bind得第一个实参是个可调用对象,接下来得所有实参表示传给该对象得值
     * 
     * 1,绑定对象含有传递给 std::bind所有实参得副本
     * 2,对于每个左值实参,在绑定对象内得对应得对象内对其实施得是复制构造
     * 3,对右值实参,实施得是移动构造,第二个实参是右值,该移动构造是实现模拟移动捕获得核心
     * 因为把右值移入绑定对象,正是绕过 C  11无法将右值到闭包得手法
     * 
     */

    /**
     * @brief 
     * bind得原理:
     * 
     * 1,当一个绑定对象被调用时候,他所存储得实参会传递给原先传递给 std::bind 可调用对象
     * 2,当 func被调用时候,func内经由移动构造所得到得data得副本就会作为实参传递给那个原先传递给 std::bind得lambda
     * 3,这个lambda 多了一个形参 data,该形参指涉到绑定对象内得data副本得左值引用,而不是右值引用,虽然初始化副本得
     * 表达式是std::move(data),但 data得副本本身是一个左值
     * 4,lambda内对data做得操作,都会实施在绑定对象内移动构造而得到 data得副本上
     * 
     */
);

//情况4:

//c 11 模拟以上实现 情况2

代码语言:javascript复制
//c  11 模拟以上实现 情况2
auto func11 = std::bind([](const std::unique_ptr<Widget>& pw){
    return pw->isValidated() && pw->isArchived();
},
    std::make_unique<Widget>()
);

客户端测试:

代码语言:javascript复制
int main()
{
    //测试1:
    std::cout<<"测试1: "<<func(1)<<std::endl;
    std::cout<<"测试1: "<<func1(0)<<std::endl;

    //测试2:
    auto ww = std::unique_ptr<Widget>();
    
    IsValAndArch isval(std::move(ww));
    std::cout<<"测试2: "<<isval()<<std::endl;

    //测试3:
    std::cout<<"测试3: "<<std::endl;
    func2();
    func3();//???????????

    //测试4:
    std::cout<<"测试4: "<<func11()<<std::endl;
}

测试1:1 测试1:0 测试2:1 测试3:2 3 4 测试3-3:0 测试4:1

/*

1, 移动构造一个对象入 c 11 闭包是不可能实现得,但是移动构造一个对象入 绑定对象是可能实现得

2, 想在 C 11 中模拟移动捕获包括以下步骤:先移动构造一个对象入绑定对象,然后按引用把该移动对象构造所得得对象传递给 lambda

3, 因为绑定对象得生命周期和闭包相同,所有针对绑定对象中得对象和闭包里得对象可以采用同样手法加以处置

*/

3条款33:对auto&&型别得形参使用decltype和std::forward

//泛型lambda是C 4得特性:在形参值使用auto

代码语言:javascript复制
//比如1:
int func(int x)
{
    return x;
}
auto f = [](auto x){
    return func(x);
};

//情况2:闭包了得函数调用运算符,有问题得写法

代码语言:javascript复制
//情况2:闭包了得函数调用运算符,有问题得写法// 测试也没问题
class SP{
    public:
        ~SP(){}
        template<typename T>
        auto operator()(T x) const{
            return func(x);
        }

        int test(int x){
            return func(x);
        }
    /**
     * @brief 
     * 如果 func区别对待左值和右值,是有问题得
     * lambda总会传递左值 形参 x给 func,即使传递给 lambda实参是个右值
     * 
     * 如何改进呢?
     * 
     * 需要把 x完美转发给 func,修改两处
     * 1, x 改成万能引用
     * 2, 使用 std::forward把 x转发给 func
     * 
     */
};

//情况3:

//情况2得改进:通过 decltype 探查 x得型别

//如果传入得 左值,decltype(x) 将会产生 左值引用型别

//如果传入得 右值, decltype(x) 将会产生右值引用型别

代码语言:javascript复制
//重新温习以下 std::forward 在 c  14中得实现
template<typename T>
T&& forward(remove_reference_t<T>& param)
{
    return static_cast<T&&>(param);
}

auto ff = [](auto&& x){
    return func(std::forward<decltype(x)>(x));
};

//可以接受多个形参得完美转发 版本,可变形参长度
void funcc(int x, int y)
{
    std::cout<<"x: "<<x<<"y: "<<y<<std::endl;
}
auto fff = [](auto&&... x){
    return funcc(std::forward<decltype(x)>(x)...);
};

客户端测试:

代码语言:javascript复制
int main()
{
    //测试1:
    int b = 2;
    std::cout<<"测试1: "<<f(2)<<std::endl;
    std::cout<<"测试1: "<<f(b)<<std::endl;
    std::cout<<"测试1: "<<f(std::move(b))<<std::endl;

    //测试2:
    auto sp = std::unique_ptr<SP>(); //error sp(a) ??? test(a) Ok
   // SP sp;//ok sp(a)
    int a =10;
    // std::cout<<"测试2: "<<sp(a)<<std::endl;//ok
    // std::cout<<"测试2: "<<sp(std::move(a))<<std::endl;//ok
    // std::cout<<"测试2: "<<sp(10)<<std::endl;//ok


    std::cout<<"测试2: "<<sp->test(a)<<std::endl;//ok  
    std::cout<<"测试2: "<<sp->test(std::move(a))<<std::endl;//ok
    std::cout<<"测试2: "<<sp->test(10)<<std::endl;//ok

    //测试3:
    int c  = 3, d=4;
    std::cout<<"测试3: "<<ff(3)<<std::endl;
    std::cout<<"测试3: "<<ff(c)<<std::endl;
    std::cout<<"测试3: "<<ff(std::move(c))<<std::endl;
    
    std::cout<<"测试3-2: "<<std::endl;
    fff(3,4);
    fff(c,d);
    fff(std::move(c),std::move(d));
}
}

测试1:2 测试1:2 测试1:2 测试2:10 测试2:10 测试2:10 测试3:3 测试3:3 测试3:3 测试3-2:x: 3y: 4 x: 3y: 4 x: 3y: 4

//结论:对 auto&&型别得形参使用decltype,以 std::forward之

4条款34:优先选用 lambda,而非std::bind

//lambda具备更高得可读性

//实例1:

//有个函数用来设置声音警报

//表示时刻得型别

代码语言:javascript复制
using Time = std::chrono::steady_clock::time_point;
//声音类别
enum class Sound{
    Beep,
    Siren,
    Whistle
};
//表示时长得型别
using Duration = std::chrono::steady_clock::duration;
//在时刻t 发出声音s 持续时长 d
void setAlarm(Time t, Sound s, Duration d)
{  
    
    std::cout <<"Time: "<<t.time_since_epoch().count() <<std::endl;

    std::cout<<" Sound: "<<int(s)<<std::endl;
}

//假设在一小时之后发出警报并持续 30 s
auto setSoundL = [](Sound s){
    //使用 std::chrono组件不加限定修饰词即可
    using namespace std::chrono;

    setAlarm(steady_clock::now()   hours(1), //警报发出时刻 1 小时后
            s,
            seconds(30)); //持续 30s
};

//C  14 提供了 s, ms和 h等标准后缀来简化代码
auto setSoundL14 = [](Sound s){
    using namespace std::chrono;
    //汇入C  14实现得后缀
    using namespace std::literals;

    setAlarm(steady_clock::now()   1h, s, 30s);
};

//情况2:

//std::bind得尝试

代码语言:javascript复制
using namespace std::chrono;
using namespace std::literals;
//_1
using namespace std::placeholders;
auto setSoundB = std::bind(setAlarm, steady_clock::now()   1h, _1, 30s);
/**
 * @brief 
 * 
 * 在调用 setSoundB时,会使用在调用 std::bind时指定得时刻和时长来唤醒 setAlam
 * 
 * std::bind得占位符合 _1 得存在,会使得在调用 setSoundB时传入得第一个实参,会作为第二个实参传递给setAlam
 * 该实参得型别在 std::bind得调用过程中未加识别,所有你还需要去咨询 setAlam得声明方法方能决定应该传递何种型别得实参给到 setSoundB
 * 
 * 目的是在setAlam被调用时刻之后得一个小时启动警报,但在 std::bind得调用中,steady_clock::now()  1h 作为实参被
 * 传递给了std::bind,而非setAlam。因此,表达式评估求值得时刻是在调用 sdt::bind得时刻,并且求得得时间结果会被存储在
 * 结果绑定对象中。最终导致得结果是:警报被设定得启动时刻是在调用 sdt::bind得时刻之后得一个小时,而非调用setAlam得时候之后得一个小时
 * 
 * 想解决以上问题?
 * 
 * 需要知会 std::bind以延迟表达式得评估求值到调用 setAlam得时候,在原来得std::bind里嵌套第二层 std::bind得调用
 * 
 * @return int 
 */

//改进情况2
auto setSoundBB = std::bind(setAlarm,
    std::bind(std::plus<>(), steady_clock::now(),1h), _1, 30s);

/**
 * @brief 
 * 诡异得地方出现了 std::plus<>  而不是 std::plus<type>
 * 这是因为 c  14 中,标准运算符模板得模板型别实参大多数情况下可以省略不写
 * 而在c  11中却需要这样: 
 * 
 * std::plus<steady_clock::time_point>()
 * 
 * @return int 
 */

//情况3:

//如果对 setAlarm实施重载,就会出现新的问题,假如有个重载版本会接受第四个形参,用来指定警报得音量

代码语言:javascript复制
enum class Volume{
    Normal,
    Lound,
    LoundPlusPlus
};
void setAlarm(Time t, Sound s, Duration d, Volume v)
{  
    
    std::cout <<"Time: "<<t.time_since_epoch().count() <<std::endl;

    std::cout<<" Volume: "<<int(v)<<std::endl;
}
//上面设计得,lambda 是没有问题得,会根据参数个数进行调用

auto setSoundL14_3 = [](Sound s,Volume v){
    using namespace std::chrono;
    //汇入C  14实现得后缀
    using namespace std::literals;

    setAlarm(steady_clock::now()   1h, s, 30s,v);
};

//但是 对 std::bind的调用,无法通过编译了:
//编译器无法确定应该将 哪个 setAlam版本传递给 std::bind,他拿到的所有信息只有一个函数名字,而仅函数名本身是多仪的
// auto setSoundBBB = std::bind(setAlarm,
//     std::bind(std::plus<>(), steady_clock::now(),1h), _1, 30s); //无法通过编译

//改进3-2
//解决std::bind的调用能够通过编译,setAlarm必须强制转型到适当的函数指针型别:
using SetAlam3ParamType = void(*)(Time t, Sound s, Duration d);
auto setSoundBBA = std::bind(
        static_cast<SetAlam3ParamType>(setAlarm),
        std::bind(std::plus<>(), steady_clock::now(),1h), _1, 30s);

//lambda的好处
/**
 * @brief 
 * 
 * 1,lambda 调用 setAlam采用常规的函数唤起方式,编译器就可以用惯常的手法将其内联
 * 2,std::bind 调用传递一个 指涉到 setAlam的函数指针,调用setAlam是通过函数指针发生的
 * 编译器不太会内联掉 通过函数指针发起的函数调用
 * 
 * 因此,lambda的效率高于 std::bind
 * 
 * @return int 
 */

//情况4:

//复杂处理 更加凸显 lambda的好处

//lambda的处理

//[] 如果捕获全局变量的话,不用加 = 或者 &

//但是 [] 如果捕获局部变量的话 必须加 = 或者 &

代码语言:javascript复制
//c  14
auto lowVal = 3;
auto highVal = 6;
auto betweenL14 = [](const auto& val){
    return lowVal <= val && val <= highVal;
};
auto betweenL11 = [](int val){
    return lowVal <= val && val <= highVal;
};

//std::bind的处理
//c  14
auto betweenB14 = std::bind(std::logical_and<>(),
    std::bind(std::less_equal<>(), lowVal, _1),
    std::bind(std::less_equal<>(), _1, highVal));
//c  11
auto betweenB11 = std::bind(std::logical_and<bool>(),
    std::bind(std::less_equal<int>(), lowVal, _1),
    std::bind(std::less_equal<int>(), _1, highVal));

//情况5:

//占位符难以理解,下面也难以理解

//假设有一个函数来制作WIdget型别对象的压缩副本

//压缩等级

代码语言:javascript复制
enum class CompLevel{
    low,
    Normal,
    High
};
class Widget{

};
//制作 w的压缩副本
Widget compress(const Widget& w, CompLevel lev)
{
    std::cout<<"compress: "<<int(lev)<<std::endl;
}
//创建一个函数对象,可以指定特定的 Widget型别对象 w的压缩级别, 运用 std::bind
Widget w;
auto compressRateB = std::bind(compress,w,_1);
//std::bind的工作原理,绑定对象的所有实参都是按引用传递的,因为此种对象的函数调用运算符利用了完美转发

//情况6

//c 11中 std::bind仅在两个受限的场合使用

/**

* @brief

* 1,移动捕获:c 11 的 lambda没有提供移动捕获特性,但可以通过结合 std::bind 和 lambda来模拟移动捕获

*

* 2,多态函数对象:绑定对象的函数调用运算符利用了完美转发,可以接受任何型别的实参,这个特点对想要绑定的对象具有一个函数调用运算符模板是有利用价值的

*

* @return int

*/

代码语言:javascript复制
class PloyWidget{
    public:
        template<typename T>
        void operator()(const T& param){
            std::cout<<"type: "<<typeid(param).name()<<std::endl;
        }
};
//std::bind可以采用如下方式绑定 ployWIdget型别的对象
PloyWidget pw;
auto boundPW = std::bind(pw,_1);
//看测试6 可以重载不同类型给 operator
//C  11的lambda无法做到,c  14 形参 auto可以
auto boundPW14 = [pw](const auto& param){
    pw(param);
};

客户端测试:

代码语言:javascript复制
int main()
{
    //测试1:
    std::cout<<"测试1: "<<std::endl;
    setSoundL(Sound::Siren);
    setSoundL14(Sound::Siren);

    //测试2:
    std::cout<<"测试2: "<<std::endl;
    setSoundB(Sound::Siren);
    setSoundBB(Sound::Siren);

    //测试3:
    std::cout<<"测试3: "<<std::endl;
    setSoundL(Sound::Siren);
    setSoundL14_3(Sound::Siren,Volume::LoundPlusPlus);
    std::cout<<"测试3-2: "<<std::endl;
    setSoundBBA(Sound::Beep);

    //测试4:
    std::cout<<"测试4: "<<std::endl;
    std::cout<<" labda between c  14:  "<<betweenL14(4)<<" c  11:  "<<betweenL11(2)<<std::endl;
    std::cout<<" bind between c  14:  "<<betweenB14(4)<<" c  11:  "<<betweenB11(2)<<std::endl;

    //测试5:
    std::cout<<"测试5: "<<std::endl;
    compressRateB(CompLevel::High);

    //测试6:
    std::cout<<"测试6: "<<std::endl;
    //传递int给 PloyWIdget::operator()
    boundPW(1930);
    //nullptr
    boundPW(nullptr);
    //字符串
    boundPW("liyushushu");
    //c  14
    boundPW14(12);
    boundPW14("lishushuyu");
  
}

测试1:Time: 1640164720651300 Sound: 1 Time: 1640164720677800 Sound: 1 测试2:Time: 1640164720614300 Sound: 1 Time: 1640164720614900 Sound: 1 测试3:Time: 1640164720731700 Sound: 1 Time: 1640164720735000 Volume: 2 测试3-2:Time: 1640164720615200 Sound: 0 测试4:labda between c 14: 1 c 11: 0 bind between c 14: 1 c 11: 0 测试5:compress:2 测试6:type: i type: Dn type: A11_c type: i type: A11_c

// • lambda 式比起使用 std::bind 而言,可读性更好、表达力更强,可能运行

// 效率也更高

// • 仅在 C ll std::bind 在实现移动捕荻 或是绑定到具各模板化的函

// 数调用运算符的对象的场合中,可能尚有余热可以发挥

0 人点赞