C++11中的右值引用、智能指针、function、bind、lambda、多线程

右值引用

通过在构造函数中分配动态内存,并将字符串的副本存储在堆上,可以确保字符串的生存期与对象的生存期相匹配。同时,使用 newdelete 确保了对内存的正确管理,避免了内存泄漏。究其原因是指针变量,不能浅拷贝,否则会出现当对象销毁时,指针指向的是一个无效的内存地址,这可能导致程序崩溃或者未定义的行为。或者出现内存重复释放的可能

const左值引用 = 右值引用,所以为什么很多带左值引用的参数都加了const,右值不能带const,右值引用变量是左值,数组(指针,原因见上)都是通过new弄的,字符数组需要new len + 1,声明友元函数,函数内部对象可以就访问自己的私有成员

number operator+(number& n) // 加号运算符重载函数 这个是类内实现的,只有一个参数
number operator+(number& n1, number& n2) 这个是类外实现的,有两个参数:全局函数重载 + 号
使用都一样:n3 = n1 + n2;

右值引用的拷贝构造和赋值:当传入参数或返回返回参数是临时对象的时候,就会自动调用,因为是临时对象,所以我们可以设计右值引用的拷贝构造和赋值里面不做堆内存的开辟,只是做浅拷贝即可,然而,有时候编译器对其C++进行了优化,就如C++的构造方法以及三条优化规则-CSDN博客那样:对临时对象的拷贝构造直接调用了普通构造函数,所以肉眼感觉没有发生右值引用

CMystring func2(CMystring& str)
{
    const char* ptst = str.c_str();
    CMystring temstr(ptst);
    return temstr; //如果存在右值拷贝就会调用右值拷贝,如果没有就调用左值拷贝(const &),因为temstr它可以认为是一个临时变量,老师说的,我也有点不理解,难道是因为是栈帧上的临时对象接收了它?
}

std::move(): 将左值变为右值:移动语义,有时候传入右值引用,他就变成了左值,我们可以通过move让它变回去,但是传到下一个函数就右变成了左值:所以采用 std::forward():类型的完美的转发:它可以根据参数的本身定义类型返回它的类型,是左值就返回左值,右值就返回右值

用右值引用作为参数的好处:比如临时变量的拷贝构造避免了资源浪费        

1:直接接收右值,避免不必要的复制操作
2:既可以左值又可以右值:并采用forward自动识别:
  void push(Ty &&val) 函数模板的类型推演 + 引用折叠: Ty CMystring & + && = &
                                                  Ty CMystring && + && = &&         

智能指针

unique_ptr有一点和scoped_ptr做的一样,就是去掉了拷贝构造函数和operator=赋值重载函数,禁止用户对unique_ptr进行显示的拷贝构造和赋值,防止智能指针浅拷贝问题的发生。

但是unique_ptr提供了带右值引用参数的拷贝构造和赋值,也就是说,unique_ptr智能指针可以通过右值引用进行拷贝构造和赋值操作,或者在产生unique_ptr临时对象的地方,如把unique_ptr作为函数的返回值时,示例代码如下:

// 示例1
unique_ptr<int> ptr(new int);
unique_ptr<int> ptr2 = std::move(ptr); // 使用了右值引用的拷贝构造
ptr2 = std::move(ptr); // 使用了右值引用的operator=赋值重载函数
此时ptr还是不能用了,为空了,move可以让你知道资源转移了

强智能指针share_ptr和弱智能指针weak_ptr的使用和区别:定义对象时,用强智能指针shared_ptr,在其它地方引用对象或参数接收时,使用弱智能指针weak_ptrweak_ptr是一个观察者的角度,观察还活着没,他拥有它所观察的对象的智能指针的引用计数,但不会改变,只是局外人,所以循环引用时,就用弱智能指针,在多线程中:看链接吧。

/* 如果想访问对象的方法,先通过pw的lock方法进行提升操作,把weak_ptr提升 为shared_ptr强智能指针,提升过程中,是通过检测它所观察的强智能指针保存 的Test对象的引用计数,来判定Test对象是否存活,ps如果为nullptr,说明Test对象 已经析构,不能再访问;如果ps!=nullptr,表示我要使用它,马上我就给她的引用计数+1, 然后使用它 */

    shared_ptr<Test> ps = pw.lock();
    if (ps != nullptr)
    {
        ps->show();
    }

函数对象和绑定器

function:作用包装任何可以调用的对象: 绑定器(返回的结果还是一个函数对象)、函数对象、lambda表达式(就是函数对象,只不过自动重载了())普通函数、成员函数

绑定器:绑定器是指可以将函数和部分参数绑定在一起,形成一个新的函数对象的工具

int sum(int a, int b){return a+ b;}
function<int (int, int)> func1 = sum; //为指针,但是function自动将外部普通函数的名字变成了函数指针,对bind同理
func1(10, 2);
​
function<void (Test*, string) func2 = &Test::hello;//为指针,成员函数必须显式地取得成员函数的地址,对bind同理
func(&Test(), "hello"); 
​
function<int (int, int)> func = bind(sum, 10, _1);//返回的也是一个function                                                                     类型
func(20);
​
pool_.push_back(new Tread(bind(&ThreadPool::TreadFunc, this, _1))); 成员函数是不可以充当线程函数的,所以可以用用bind绑定成函数对象

function好处:封装,可以存储很多不同的同类型的函数对象

map<int, function<void ()> > funcMap;// 可以这样装绑定器、普通函数、成员函数、函数对象、lambda表达式
但是  typedef void (*FuncPtr)(); 
    map<int, FuncPtr> funcMap;  只能普通函数或成员函数,不能装绑定器、函数对象、lambda表达式

函数对象的升级版-》lambda表达式,更简洁

auto func1 = [](int a, int b) ->int {return a+ b;}
int res = func1(10, 20);
[=]:以传值的方式捕获外部所有变量
[&]:以传引用的方式捕获外部所有变量
sort(vec.begin(), vec.end(), [](int a, int b) -> bool{
        return a > b;
});
auto it = find_if(vec.begin(), vec.end(), [](int a) -> bool{
        return a < 60;
});
for_each(vec.begin(), vec.end(), [](int& a) -> void { // 引用传递参数
    a =  a * 10;
});
for_each(vc.begin(), vc.end(), [&count](int x) {
    if (x % 3 == 0) {
        count++;
    }
});

常方法中只能读成员变量,不能修改

多线程thread

C++11之前,C++库中没有提供和线程相关的类或者接口,因此在编写多线程程序时,Windows上需要调用CreateThread创建线程,Linux下需要调用clone或者pthread线程库的接口函数pthread_create来创建线程。但是这样是直接调用了系统相关的API函数,编写的代码,无法做到跨平台编译运行。

C++11之后提供了thread线程类,可以很方便的编写多线程程序(注意:编译器需要支持C++11之后的语法,推荐VS2017,g++4.6版本以上),代码示例如下:

#include <iostream>
#include <thread>
#include <string>
using namespace std;

// 线程1的线程函数
void threadProc1()
{
    cout << "thread-1 run begin!" << endl;
    // 线程1睡眠2秒
    std::this_thread::sleep_for(std::chrono::seconds(2));
    cout << "thread-1 2秒唤醒,run end!" << endl;
}

// 线程2的线程函数
void threadProc2(int val, string info)
{
    cout << "thread-2 run begin!" << endl;
    cout << "thread-2 args[val:" << val << ",info:" << info << "]" << endl;
    // 线程2睡眠4秒
    std::this_thread::sleep_for(std::chrono::seconds(4));
    cout << "thread-2 4秒唤醒,run end!" << endl;
}
int main()
{
    cout << "main thread begin!" << endl;

    // 创建thread线程对象,传入线程函数和参数,线程直接启动运行
    thread t(threadProc1);
    thread t1(threadProc2, 20, "hello world");

    // 等待线程t和t1执行完,main线程再继续运行
    t.join();
    t1.join();

    cout << "main thread end!" << endl;
    return 0;
}

代码运行打印如下:

main thread begin!
thread-1 run begin!
thread-2 run begin!
thread-2 args[val:20,info:hello world]
thread-1 2秒唤醒,run end!
thread-2 4秒唤醒,run end!
main thread end!

可以看到,在C++语言层面编写多线程程序,用thread线程类非常简单,定义thread对象,只需要传入相应的线程函数和参数就可以了。

上面同样的代码在Linux平台下面用g++编译:
g++ 源文件名字.cpp -lpthread
【注意】:需要链接pthread线程动态库,所以C++的thread类在Linux环境下使用的就是pthread线程库的相关接口。

线程互斥

在多线程环境中运行的代码段,需要考虑是否存在竞态条件,如果存在竞态条件,我们就说该代码段不是线程安全的,不能直接运行在多线程环境当中,对于这样的代码段,我们经常称之为临界区资源,对于临界区资源,多线程环境下需要保证它以原子操作执行,要保证临界区的原子操作,就需要用到线程间的互斥操作-锁机制,thread类库还提供了更轻量级的基于CAS操作的原子操作类。

下面用模拟3个窗口同时卖票的场景,用代码示例一下线程间的互斥操作。
thread线程类库的互斥锁mutex

下面这段代码,启动三个线程模拟三个窗口同时卖票,总票数是100张,由于整数的- -操作不是线程安全的操作,因为多线程环境中,需要通过加互斥锁做到线程安全,代码如下示例:

// 车票总数是100张
volatile int tickets = 100;
// 全局的互斥锁
std::mutex mtx;

// 线程函数
void sellTicketTask(std::string wndName)
{
    while (tickets > 0)
    {
        // 获取互斥锁资源
        mtx.lock();
        if (tickets > 0)
        {
            std::cout << wndName << " 售卖第" << tickets << "张票" << std::endl;
            tickets--;
        }
        // 释放互斥锁资源
        mtx.unlock();

        // 每卖出一张票,睡眠100ms,让每个窗口都有机会卖票
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

// 模拟车站窗口卖票,使用C++11 线程互斥锁mutex
int main()
{
    // 创建三个模拟窗口卖票线程
    std::thread t1(sellTicketTask, "车票窗口一");
    std::thread t2(sellTicketTask, "车票窗口二");
    std::thread t3(sellTicketTask, "车票窗口三");

    // 等待三个线程执行完成
    t1.join();
    t2.join();
    t3.join();

    return 0;
}

通过上面的代码可以看到,C++11的mutex和Linux平台下pthread线程库的pthread_mutex_t互斥锁使用几乎是一样的(实际上在Linux平台下mutex就是调用的pthread_mutex_t互斥锁相关的系统函数),mutex也支持trylock活锁机制,可以自己进行测试。
thread线程类库基于CAS的原子类

实际上,上面代码中因为tickets车票数量是整数,因此它的- -操作需要在多线程环境下添加互斥操作,但是mutex互斥锁毕竟比较重,对于系统消耗有些大,C++11的thread类库提供了针对简单类型的原子操作类,如std::atomic_int,atomic_long,atomic_bool等,它们值的增减都是基于CAS操作的,既保证了线程安全,效率还非常高。

下面代码示例开启10个线程,每个线程对整数增加1000次,保证线程安全的情况下,应该加到10000次,这种情况下,可以用atomic_int来实现,代码示例如下:

#include <iostream>
#include <atomic> // C++11线程库提供的原子类
#include <thread> // C++线程类库的头文件
#include <vector>

// 原子整形,CAS操作保证给count自增自减的原子操作
std::atomic_int count = 0;

// 线程函数
void sumTask()
{
    // 每个线程给count加1000次
    for (int i = 0; i < 1000; ++i)
    {
        count++;
    }
}

int main()
{
    // 创建10个线程放在容器当中
    std::vector<std::thread> vec;
    for (int i = 0; i < 10; ++i)
    {
        vec.push_back(std::thread(sumTask));
    }

    // 等待线程执行完成
    for (int i = 0; i < vec.size(); ++i)
    {
        vec[i].join();
    }

    // 所有子线程运行结束,count的结果每次运行应该都是10000
    std::cout << "count : " << count << std::endl;

    return 0;
}

实际上,C++11类库的原子操作类,在Linux平台下调用的也是CAS(compare_and_set)相关的系统接口。

线程同步通信

多线程在运行过程中,各个线程都是随着OS的调度算法,占用CPU时间片来执行指令做事情,每个线程的运行完全没有顺序可言。但是在某些应用场景下,一个线程需要等待另外一个线程的运行结果,才能继续往下执行,这就需要涉及线程之间的同步通信机制。

线程间同步通信最典型的例子就是生产者-消费者模型,生产者线程生产出产品以后,会通知消费者线程去消费产品;如果消费者线程去消费产品,发现还没有产品生产出来,它需要通知生产者线程赶快生产产品,等生产者线程生产出产品以后,消费者线程才能继续往下执行。

C++11 线程库提供的条件变量condition_variable,就是Linux平台下的Condition Variable机制,用于解决线程间的同步通信问题,下面通过代码演示一个生产者-消费者线程模型,仔细分析代码:

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
#include <vector>

// 定义互斥锁(条件变量需要和互斥锁一起使用)
std::mutex mtx;
// 定义条件变量(用来做线程间的同步通信)
std::condition_variable cv;
// 定义vector容器,作为生产者和消费者共享的容器
std::vector<int> vec;

// 生产者线程函数
void producer()
{
    // 生产者每生产一个,就通知消费者消费一个
    for (int i = 1; i <= 10; ++i)
    {
        // 获取mtx互斥锁资源
        std::unique_lock<std::mutex> lock(mtx);

        // 如果容器不为空,代表还有产品未消费,等待消费者线程消费完,再生产
        while (!vec.empty())
        {
            // 判断容器不为空,进入等待条件变量的状态,释放mtx锁,
            // 让消费者线程抢到锁能够去消费产品
            cv.wait(lock);
        }
        vec.push_back(i); // 表示生产者生产的产品序号i
        std::cout << "producer生产产品:" << i << std::endl;

        /*
        生产者线程生产完产品,通知等待在cv条件变量上的消费者线程,
        可以开始消费产品了,然后释放锁mtx
        */
        cv.notify_all();

        // 生产一个产品,睡眠100ms
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}
// 消费者线程函数
void consumer()
{
    // 消费者每消费一个,就通知生产者生产一个
    for (int i = 1; i <= 10; ++i)
    {
        // 获取mtx互斥锁资源
        std::unique_lock<std::mutex> lock(mtx);

        // 如果容器为空,代表还有没有产品可消费,等待生产者生产,再消费
        while (vec.empty())
        {
            // 判断容器为空,进入等待条件变量的状态,释放mtx锁,
            // 让生产者线程抢到锁能够去生产产品
            cv.wait(lock);
        }
        int data = vec.back(); // 表示消费者消费的产品序号i
        vec.pop_back();
        std::cout << "consumer消费产品:" << data << std::endl;

        /*
        消费者消费完产品,通知等待在cv条件变量上的生产者线程,
        可以开始生产产品了,然后释放锁mtx
        */
        cv.notify_all();

        // 消费一个产品,睡眠100ms
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}
int main()
{
    // 创建生产者和消费者线程
    std::thread t1(producer);
    std::thread t2(consumer);

    // main主线程等待所有子线程执行完
    t1.join();
    t2.join();

    return 0;
}

相关推荐

  1. C++11_引用

    2024-05-13 23:46:02       29 阅读
  2. 为什么C++11还要复杂提出引用

    2024-05-13 23:46:02       11 阅读
  3. C/C++ 15C++11引用

    2024-05-13 23:46:02       28 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-05-13 23:46:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-05-13 23:46:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-05-13 23:46:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-05-13 23:46:02       20 阅读

热门阅读

  1. Redis——RDB、AOF和混合持久化机制

    2024-05-13 23:46:02       13 阅读
  2. @Validated校验多层json

    2024-05-13 23:46:02       13 阅读
  3. centos7下vim命令笔记-查找字符

    2024-05-13 23:46:02       12 阅读
  4. 【C++】防止头文件被重复包含

    2024-05-13 23:46:02       14 阅读
  5. Cocos Creator 3.8.x报错:5302

    2024-05-13 23:46:02       12 阅读
  6. 消息 队列

    2024-05-13 23:46:02       15 阅读
  7. 在vue3中测试执行typescript代码片段

    2024-05-13 23:46:02       13 阅读