C++ 学习笔记:lambda表达式 与 单例模式

        利用lambda表达式作为容器的成员

        可以先传入 function<int(int,int)> 函数类型,再在名义的名字之后进行列表初始化(初始化map)如:        

int main(){
    
    unordered_map< string, function<int(int, int)> > function_table
    {
        {    "+",    [](int x, int y) {return x + y;}    },
        {    "-",    [](int x, int y) {return x - y;}    },
        {    "*",    [](int x, int y) {return x * y;}    },
        {    "/",    [](int x, int y) {return x / y;}    }
    };

    vector<function<int(int, int)>> vec{
        {  [](int x, int y) {return x + y;}    },
        {  [](int x, int y) {return x - y;}    },
        {  [](int x, int y) {return x * y;}    },
        {  [](int x, int y) {return x / y;}    }
    };

//--------------------------------------------------------------------------    

    auto cmp = [](const pair<int, int>& a, const pair<int, int>& b) 
    { return a.first < b.first; };
    map<int, int, decltype(cmp)> map{ cmp };

    cout << typeid(decltype(vec[0])).name() << endl;
    cout << typeid(op_dict["+"]).name() << endl;
    cout << typeid(cmp).name() << endl;
    /*
    class std::function<int __cdecl(int,int)>
    class std::function<int __cdecl(int,int)>
    class <lambda_323534666ed570659d8fa84872cf1336>  
    */
}

        

        对于如上述代码所示,lambda作为容器的存储成员时,因为对于容器进行实例化的类型为function<>类型,因此传入的lambda表达式就会成为lambda的函数指针的形式在容器中进行存储。
        在上述代码中,对于下面部分的map的初始化过程时,传入的lambda表达式是作为一个比较函数来进行map对于key的排序,传入的类型是class <lambda>的闭包对象。普通的lambda表达式的类型为 class <lambda>的一个闭包类型。

        使用lambda的好处:
        1、lambda可以直接作为参数,作为参数类类型是 class lambda,好处是,可以直接捕获作用域内的变量,使用起来更加方便,省去了传参的过程
        2、因此可以使用lambda表达式作为线程的参数,可以很好的捕捉当前环境下的变量
        3、lambda本身就一直被视为一个可调用对象,可以直接在lambda表达式后跟函数参数

    

        对于单例模式来说,常见的模式有饱汉模式和饿汉模式


        饱汉模式:在static类型的类对象p定义时就进行初始化,使用时调用instance静态函数来获取类对象p。不会有多线程的冲突,但是会在不需要p时持续占用资源空间。
        饿汉模式:在类对象p定义时并不进行初始化,而是在使用时通过instance进行初始化,因此使用饿汉模式需要对于多线程的情况进行冲突处理。



class lazySingleTon {
    private:
        static lazySingleTon* p;
        lazySingleTon() {}
        static mutex lock_;
    public:
        static lazySingleTon* instance() {
            std::lock_guard<mutex> guard(lock_);
            if (!p) {
                p = new lazySingleTon();
            }
            return p;
        }

    };
    lazySingleTon* lazySingleTon::p = nullptr;
}

         进入instance函数就利用mutex互斥量来保证同一时间只有一个线程能够进入instance函数,这样的方式虽然达到了互斥的效果,但是对于第一次之后的instance函数调用,并不需要进行互斥,知识应该返回p供调用者使用,因此这样的方式浪费了大量的时间效率,

class lazySingleTon {//不上锁的话,同时调用instance会导致同时实例化两个实例并赋值给p
    private:
        static lazySingleTon* p;
        lazySingleTon() {}//把构造函数放入private 的目的是为了达到外界不能随意创建实例,必须通过instance才能够创建实例
        static mutex lock_;
    public:
        static lazySingleTon* instance() {
            //双重检查锁,只有在p为nullptr时才会进行上锁,避免了大多数的无用上锁情况
            if (!p) {
                unique_lock<mutex> _lock(lock_);// 双重检查锁,使得大多数情况不再进行加锁
                _lock.lock();
                if (!p) {
                    p = new lazySingleTon();
                }
                _lock.unlock();
            }
            return p;
        }//instance 函数只有在第一次被创建时才有必要进行加锁,因为后续不再分配和创建存储空间,而是直接返回p

        struct GarbageCollection {//内嵌垃圾回收类
            ~GarbageCollection() {
                delete lazySingleTon::p;
            }
        };
        static GarbageCollection gc;//类static成员,程序结束时进行自动析构,因此会调用GC类的析构函数,从而delete 实例p
    };
    lazySingleTon* lazySingleTon::p = nullptr;

        DCL双重锁机制在进入instance函数时并不是立刻将mutex进行上锁,而是先判断 p 是否是nullptr,即p是否未初始化,如果此时p未初始化,说明p需要在本次调用中进行初始化,因此应该对于mutex进行上锁保证同一时刻只有一个线程在运行instance函数


        GarbageCollection类是一个内嵌的垃圾回收类,通过GarbageCollection的析构函数来对于lazySingleTon中定义的堆变量 p 进行delete,并且定义一个类内的static CarbageCollection类型的成员,该static成员变量的生存周期在程序结束时到期,因此在程序结束时会自动调用GarbageCollection的析构函数对于 static p进行析构。


        DCL双重锁机制还可能存在的一种问题:p = new lazySingleTon();的乱序带来的问题,这句命令有三个步骤:
            1、分配存储lazySingleTon的存储空间
            2、构造lazySingleTon实例化对象
            3、将已经创建好的内存赋值给p
            这是一个正确的顺序,但是在实际过程中,只能够保证1是按照顺序最先执行的,而2和3不能够保证哪个先进行
            可能会产生,p已经赋值给了一段内存,但是该内存还未进行实例化,就有其他的线程调用instance导致返回了一个未构造完成的实例化对象           

new内存分配乱序的解决方法:
            1、遵循1、2、3的步骤依次编写代码:
            singleton *tmp = static_cast<singleton *>(operator new(sizeof(singleton)));
            new(p)singleton();
            p = tmp;
            
            2、使用硬件进行隔离屏蔽:
            #define barrier() __asm__ volatile ("lwsync") 在 p = new lazySingleTon(); 前调用barrier()进行隔离

            3、C++11之后的一种简洁写法:返回局部的static成员
           

singleton *singleton::instance() {
      static singleton p;
//不再在类内定义p指针,而是直接在instance内定义和初始化p,并返回p的地址
       return &p;
}


            C++ 11规定了一个线程在初始化一个变量的时候,其他线程只有等待该初始化完成之后才能进行访问

            4、Automic C++ 11 之后提供了Automic ,可以定义一个原子类型 ,对于原子类型的操作都将是原子操作,自带加锁
            std::atomic_thread_fence(std::memory_order_acquire); 获取是内存读操作,任何在此后面的内存操作不允许重排到前面
            std::atomic_thread_fence(std::memory_order_release); 释放是内存写操作,任何在此前面的内存操作不允许重排到后面
            memory_order_relaxed:松散内存序,只用来保证对原子对象的操作是原子的
            在执行 p = new lazySingleTon(); 语句之前调用 acquire 操作,执行之后调用release操作,可以保证该内存调用操作是原子的,不会发生调用前使用和调用后使用的情况
            
            5、 使用pthread_once:

            将 执行线程 &pthread_once_t 与 执行函数 初始化函数init 绑定到pthread_once函数,保证只被执行一次,如下:

class singleton {
private:
    singleton(); //私有构造函数,不允许使用者自己生成对象
    singleton(const singleton &other);
    //要写成静态方法的原因:类成员函数隐含传递this指针(第一个参数)
    static void init() {
        p = new singleton();
    }
    static pthread_once_t ponce_;
    static singleton *p; //静态成员变量 

public:
    singleton *instance() {
        // init函数只会执行一次
        pthread_once(&ponce_, &singleton::init);
        return p;
    }
};

小的知识点:    

        1、mutex m.lock m.unlock 是最基本的互斥锁
        lock_guard 是一个管理mutex的类,声明lock_guard<mutex> 对象,会在该对象的作用域内对该mutex进行上锁,作用域结束解锁
       unique_lock 与lock_guard的区别在于 在生成unique_lock的时候不会对mutex立即上锁,可以灵活加锁和解锁
        mutex是最原始的,而lock_guard和unique_lcok都是对于 对象变量的管理类

        

        2、ostringstream 的使用:可以讲内容输出到ostringstream中,然后通过调用成员函数.str()将输出的内容全部取出保存为字符串,即将ostringstream视为一个缓冲容器

        

        3、thread的两个成员函数 .join()  和 .detach() join函数被调用时,主线程会阻塞于此,直到等待被调用join成员函数的线程执行完毕之后主线程才会继续执行。普通情况下,主线程会等待其子线程全部结束之后再结束,而对子线程调用detach之后主线程便可以提前结束,不需要等待该子线程。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Lambda表达式是C++11引入的一种函数对象,可以在需要函数对象的地方使用,比如作为函数参数、返回值等。Lambda表达式的语法形式如下: ``` [capture list](parameters) mutable exception -> return type { // function body } ``` 其中,`capture list` 表示捕获列表,用于捕获外部变量。`parameters` 表示函数参数列表,`mutable` 用于表示是否可以修改值传递的变量,`exception` 是异常列表,`return type` 表示返回类型,`function body` 则是函数体。 在Lambda表达式中,可以通过 `[this]` 捕获当前对象的指针,即 `this` 指针,可以方便地访问当前对象的成员变量和成员函数。例如: ``` class MyClass { public: void foo() { int x = 1; auto lambda = [this, x]() mutable { this->m_member_var += x; this->m_member_function(); x++; }; lambda(); } private: int m_member_var; void m_member_function(); }; ``` 在上面的例子中,Lambda表达式通过 `[this, x]` 捕获了当前对象的指针和 `foo()` 函数中定义的变量 `x`。在 Lambda 表达式中可以通过 `this->m_member_var` 和 `this->m_member_function()` 访问当前对象的成员变量和成员函数。由于 `x` 是值传递的,所以在 Lambda 表达式中需要使用 `mutable` 关键字使其可修改,可以通过 `x++` 修改变量的值。最后调用 `lambda()` 执行 Lambda 表达式。 需要注意的是,Lambda表达式捕获 `this` 指针时,需要保证当前对象是有效的,即不能在已经销毁的对象中访问成员变量和成员函数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值