• C++11的闭包(lambda、function、bind)


    c++11开始支持闭包,闭包:与函数A调用函数B相比较,闭包中函数A调用函数B,可以不通过函数A给函数B传递函数参数,而使函数B可以访问函数A的上下文环境才可见(函数A可直接访问到)的变量;比如:

        函数B(void) {

            ......

        }

        函数A {

            int a = 10;

            B();    //普通调用函数B

        }

        函数B无法访问a;但如果是按闭包的方式,则可以访问变量a:

        函数A() {

            int a = 10;

            auto closure_B = [a]() {

                ......

            }

        }

        所以闭包的优缺点很清晰,都是同一个:可以不通过传参获取调用者的上下文环境;

       

        下面开始介绍c++11的闭包所涉及的内容:

        1、lambda

        1.1、lambda的定义

        1.2、lambda的应用

        1.3、lambda的使用注意事项

        2、function

        2.1、通过lambda的function

        2.2、通过函数的function



        1、lambda

        1.1、lambda的定义:

            匿名表达式的结构:auto lambda = [捕获列表] (参数列表) (修饰符) {匿名函数体};

            关于捕获列表:捕获调用者上下文环境的需要访问的变量,可按值捕获或按引用捕获,其规则如下:

            []:什么也不捕获

            [=]:捕获所有一切变量,都按值捕获

            [&]:捕获所有一切变量,都按引用捕获

            [=, &a]:捕获所有一切变量,除了a按引用捕获,其余都按值捕获

            [&, =a]:捕获所有一切变量,除了a按值捕获,其余都按引用捕获

            [a]:只按值捕获a

            [&a]:只按引用捕获a

            [a, &b]:按值捕获a,按引用捕获b

            关于参数列表:

            和函数调用的参数列表含义完全一样;

            关于修饰符:

            mutable:必须注意加入mutable可以修改的是:在lambda匿名函数体里边,按值捕获到的变量,实质上是调用者函数中变量的只读拷贝(read-only),加入了mutable后,匿名函数体内部可以修改这个拷贝的值,也就是说调用者函数中该变量的值依然不会被改变;

    关于匿名函数体:

            和函数没有区别;

        1.2、lambda的应用:

            a. 最简单的使用:

     1 TEST (test1, lambda_1) {
     2 //capture list is empty, all the args is value, so variable b will not change.
     3     int a = 1, b = 2;
     4     auto lambda = [](int a, int b) {
     5         b = a + a + a;
     6         return a + b;
     7     };
     8  
     9     std::cout << lambda(a, b) << std::endl;
    10     std::cout << b << std::endl;
    11 }

    这里捕获列表为空,完全相当于调用普通函数

            b. 捕获列表非空:

     1 TEST (test1, lambda_2) {
     2 //capture list is not empty, x is value, y is reference, and arg z is reference
     3 //so y will change to 1, z will change to 1
     4 //x is value, so x is read-only in lambda, should not modify x in lambda
     5     int x = 1, y = 2;
     6     auto lambda = [x, &y](int &z) {
     7         z = x;
     8         y = x;
     9         //x = y;
    10         return y;
    11     };
    12  
    13     int z = 0;
    14     std::cout << lambda(z) << std::endl;
    15     std::cout << y << std::endl;
    16     std::cout << z << std::endl;
    17 }

    捕获列表非空,变量x传值,变量y传引用,并且传一个参数z;

            c. 捕获全部变量 & 使用mutable修饰符:

     1 TEST (test1, lambda_3) {
     2 //'=' in capture list means that all the variables will as value in lambda, so should not modify a, a is read-only in lambda
     3     int a = 1;
     4     auto lambda_val = [=]() {
     5         //a = 10;
     6         return a + 1;
     7     };
     8     std::cout << lambda_val() << std::endl;
     9     std::cout << a << std::endl;
    10  
    11 //'&' '=' in capture list means that all the variables will as reference in lambda, so could modify a
    12     auto lambda_ref = [&]() {
    13         a = a + 1;
    14         return a + 1;
    15     };
    16     std::cout << lambda_ref() << std::endl;
    17     std::cout << a << std::endl;
    18  
    19 //though '=' in capture list means all the variables as value in lambda_val_mutable, but lambda_val_mutable is mutable, so also could modify a
    20     auto lambda_val_mutable = [=]() mutable {
    21         a = 10;
    22         return a + 1;
    23     };
    24     std::cout << lambda_val_mutable() << std::endl;
    25     std::cout << a << std::endl;
    26 }

    使用[=]按值、[&]按引用捕获一切变量;使用mutable修饰符,在匿名函数体内部修改按值传入的变量,注意在调用者函数不会被修改生效;

            d. lambda的定义时初始化:

     1 TEST (test1, lambda_4) {
     2 //all capture variables will value in lambda, so a will not change, return a + 1 = 2
     3     int a = 1;
     4  
     5     a = 3;
     6     auto lambda_val = [=]() {
     7         //a = b;
     8         return a + 1;
     9     };
    10  
    11     int b = 10;
    12     std::cout << lambda_val() << std::endl;
    13     std::cout << a << std::endl;
    14  
    15 //a is change to 10, but for lambda_val, a is inited(read-only), will not change to 10, forever is 1
    16     a = 10;
    17     std::cout << lambda_val() << std::endl;
    18     std::cout << a << std::endl;
    19 }

    lambda表达式定义时,调用者上下文环境中的变量有哪些、值都是什么,是"一次性"初始化的,也就是说即便后面值被修改,但lambda是无法获知的,后面再创建的新的变量,lambda是无法访问的;

            e. 部分变量传引用或者传值:

     1 TEST (test1, lambda_5) {
     2 //exclude b is reference in lambda, all the other variables in capture list will be value in lambda
     3     int a = 1, b = 2, c = 3, d = 4, e = 5;
     4     auto lambda = [=, &b](int f) {
     5         b = a + c + d + e;
     6         return a + b + c + d + e;
     7     };
     8  
     9     std::cout << lambda(6) << std::endl;
    10     std::cout << b << std::endl;
    11 }

     f. 类成员函数使用lambda:

     1 TEST (test1, lambda_6) {
     2 //in a class-function, lambda's capture list is this point, so could access and modify the class non-const variable
     3     class cls {
     4         int a;
     5         int b;
     6         int c;
     7         const int d;
     8     public:
     9         cls():a(1), b(2), c(3), d(5) {}
    10         ~cls(){}
    11         void testlambda() {
    12             auto lambda = [this]() {
    13                 a = 10;
    14                 //d = 1;
    15                 return a + b + c + d;
    16             };
    17  
    18             std::cout << a << std::endl;
    19             std::cout << lambda() << std::endl;
    20             std::cout << a << std::endl;
    21         }
    22     };
    23  
    24     cls c;
    25     c.testlambda();
    26 }

     this指针进入捕获列表,匿名函数体内部即可调用类成员变量;

     g. c++11风格的lambda的使用:

    TEST (test1, lambda_7) {
        std::vector<int> v = {3,5,2,1,7,8,6,4,10,9};
     
        std::sort(begin(v), end(v), [](int i, int j) {
            return i > j;
        });
     
        std::for_each(begin(v), end(v), [](int i) {
            std::cout << i << " ";
        });
        std::cout << std::endl;
     
        int total = 0;
        for_each (begin(v), end(v), [&](int i) {
            total += i;
        });
        std::cout << total << std::endl;
     
        int base = 2;
        for_each (begin(v), end(v), [=](int i) mutable {
            total += (i * base);
            std::cout << total << "," << i << "," << base << std::endl;
        });
        std::cout << total << std::endl;
     
        for_each (begin(v), end(v), [&](int i) mutable {
            total += (i * base);
            std::cout << total << "," << i << "," << base << std::endl;
        });
        std::cout << total << std::endl;
    }

    像python一样的使用;

        1.3、lambda使用的注意事项:

            lambda常常作为线程执行函数使用,这时尤其要注意调用者上下文环境的变量(及其指向的内存空间)的生命周期,是否能够和以lambda作为线程执行函数的线程的生命周期一样长,如下面的两个例子:

            会导致出问题的:       

    TEST (test3, closure_wrong) {
        int *a = new int(10);
        std::thread th = std::thread([&a] () {
            while (1) {
                std::cout << "a: " << a << ", *a: " << *a << std::endl;
                std::this_thread::sleep_for(std::chrono::seconds(1));
            }
        });
     
        std::this_thread::sleep_for(std::chrono::seconds(3));
        delete a;
        a = nullptr;
        std::this_thread::sleep_for(std::chrono::seconds(30));//closure will collapse...
    }
            动态变量a申请了4字节的动态空间,作为线程执行函数的匿名函数使用了变量a指向的动态空间;然后a在调用者函数内部释放了动态空间,但匿名函数依然访问该动态空间,此时导致发生程序崩溃;

            不会导致出问题的:

    TEST (test3, closure_right) {
        int *a = new int(10);
        std::thread th = std::thread([a] () {
            while (1) {
                std::cout << "a: " << a << std::endl;
                std::this_thread::sleep_for(std::chrono::seconds(1));
            }
        });
     
        th.detach();
        std::this_thread::sleep_for(std::chrono::seconds(3));
        delete a;
        a = nullptr;
        std::this_thread::sleep_for(std::chrono::seconds(30));//closure will not collapse
    }
            上面的程序不会出现问题,因为匿名函数内部没有访问变量a指向的动态空间。但依然是不推荐的方式。应确保匿名函数访问的外部捕获变量,不会在线程生命周期内失效;


        2、function:

            掌握了lambda,function就非常容易掌握;c++11定义一个函数std::function,可以绑定一个匿名函数表达式lambda,也可以绑定一个普通函数(包括类成员函数);

        2.1、绑定lambda匿名函数:

    TEST (test2, function_lambda) {
        int a = 1, b = 2;
        std::function<int (const int &)> func1 = std::function<int (const int &)>([a, &b] (const int &i) {
            b = a + i;
            return a + b;
        });
     
        int c = 10;
        std::cout << func1(std::cref(c)) << std::endl;
        std::cout << a << std::endl;
        std::cout << b << std::endl;
     
        std::function<int (const int &, int &)> func2 = std::function<int (const int &, int &)>([a, &b] (const int &i, int &j) {
            int k = i + j;
            b = a + k;
            j = a + b;
            return a + b + j;
        });
     
        int d = 10, e = 20;
        std::cout << func2(d, e) << std::endl;
        std::cout << b << std::endl;
        std::cout << e << std::endl;
     
        class cls {
            int a;
            int b;
            int c;
     
        public:
            cls(int x, int y, int z):a(x), b(y), c(z){}
            ~cls(){}
     
            double CalcAverage() {
                auto calcer = std::function<double ()>([this] {
                    return (a + b + c)/3.0;
                });
     
                return calcer();
            }
        };
     
        std::vector<int> v;
        std::random_device rd;
        for (auto idx: common::Range(0, 3)) {
            int i = rd() % 10;
            v.push_back(i);
            std::cout << "input " << i << std::endl;
        }
        cls cl(v[0], v[1], v[2]);
        double average = cl.CalcAverage();
        std::cout << std::fixed << std::setprecision(6) << average << std::endl;
    }
            函数func1、func2就是绑定lambda的匿名函数;而cls的类成员函数CalcAverage,就是编写了一个匿名函数calcer再调用的闭包方式;

        2.2、绑定普通函数(包括类成员函数):

    int func1 (const int &i, const int j, int &k) {
        k = i - j;
        return i + j;
    }
    int func2 (int &i, double j, const std::string k) {
        return i + (int)j;
    }
    TEST (test2, function_simple_and_bind) {
        int i = 0, j = 1, k = 2;
        auto f1 = std::function<int (const int &, const int, int &)>(func1);
        std::cout << f1(std::cref(i), j, std::ref(k)) << std::endl;
        std::cout << k << std::endl;
        
    //if use bind and adjust the arg order, such as: arg_3 is actively arg_1, arg_1 is actively arg_2, arg_2 is actively arg_3,
    //the std::function's arg define, must obey to the active datastruct,
    //for example as follow, arg_1 is const int, arg_2 is int&, arg_3 is const int &, so f2's arg order is "const int + int & + const int &", and also when call f2.
        auto f2 = std::function<int (const int, int &, const int &)>(std::bind(func1, std::placeholders::_3, std::placeholders::_1, std::placeholders::_2));
        std::cout << f2(k, std::ref(j), std::cref(i)) << std::endl;//equal: func1(k, j, i)
        std::cout << k << std::endl;
     
        auto f3 = std::function<int (double)>(std::bind(func2, std::ref(j), std::placeholders::_1, "aaa"));
        std::cout << f3(1.1) << std::endl;
     
        class cls {
            int a;
            int b;
            int c;
     
        public:
            cls(int x, int y, int z):a(x), b(y), c(z){}
            ~cls(){}
     
            void Run() {
                int i = 4;
                auto func = std::function<void (int &)>([this, i] (int &base){
                    int times = 8;
                    while (times--) {
                        std::this_thread::sleep_for(std::chrono::seconds(1));
                        std::cout << std::fixed << std::setprecision(6) << (a + b + c + base)/i << std::endl;
                        base = a + b + c + base;
                    }
                });
     
                int base = 10;
                func(std::ref(base));
            }
        };
     
        cls cl(1, 2, 3);
        cl.Run();
     
     
        class cls2 {
            int a;
            int b;
            int c;
     
        public:
            cls2(int x, int y, int z):a(x), b(y), c(z){}
            ~cls2(){}
     
            void RealRun (int &base) {
                int times = 8;
                while (times--) {
                    std::this_thread::sleep_for(std::chrono::seconds(1));
                    std::cout << (a + b + c + base) << std::endl;
                    base = a + b + c + base;
                }
            }
     
            void Run () {
                int base = 10;
                auto f = std::bind(&cls2::RealRun, this, base);
                f();
            }
        };
     
        cls2 cl2(1, 2, 3);
        cl2.Run();
    }
            f1是直接绑定一个普通函数;

            f2是通过std::bind绑定一个普通函数,并且修改了函数传参顺序;

            f3也是通过std::bind绑定一个普通函数,进一步修改了函数传参个数;

            这里描述下std::bind的传参个数和顺序,通过bind修改传参的个数或者顺序,实现对同一个普通函数实现(包括类成员函数),有任意的自定义传参的函数变体,如对于下面的函数A:

            函数A (int, double, string) {......},可以实现如下变体:

            f1 = std::bind(&A, 1, 1.1, std::placeholders::_1)                                  //f1("abc")  ==  A(1,1.1,"abc")

            f2 = std::bind(&A, std::placeholders::_2, 1.1, std::placeholders::_1)   //f2("abc",1) ==  A(1,1.1,"abc")

          f3 = std::bind(&A, std::placeholders::_3,std::placeholders::_1,std::placeholders::_2)    //f3(1.1,"abc",1) == A(1,1.1,"abc")

            int b =1;

            f4 = std::bind(&A, b,std::placeholders::_1,"abc")    //f4(1.1) == A(1,1.1,"abc")

            f1、f2、f3对应的函数定义都是函数A的函数定义,但是实现了不同形态的函数调用"变体",方便根据具体情况自定义;

           

            cls类成员函数Run,通过lambda实现闭包;

            cls2类成员函数Run,通过bind另一个成员函数RealRun实现闭包;

  • 相关阅读:
    让sublime text3支持Vue语法高亮显示
    让sublime text3支持Vue语法高亮显示
    flutter中使用redux之多界面互动
    flutter中使用redux之多界面互动
    flutter中使用redux之多界面互动
    flutter中使用redux之多界面互动
    普通视频转高清:10个基于深度学习的超分辨率神经网络
    javaweb学习总结(二十七)——jsp简单标签开发案例和打包
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
  • 原文地址:https://www.cnblogs.com/YZFHKMS-X/p/11775144.html
Copyright © 2020-2023  润新知