C++多线程与共享指针

文章目录

  • C++多线程与共享指针
    • 一、C++多线程使用
      • 1.1 thread
      • 1.2 pthread
      • 1.3 多线程管理
      • 1.4 多线程终止问题
    • 二、共享指针shared_ptr
      • 2.1 初始化
      • 2.2 make_shared和new+shared_ptr的区别
      • 2.3 获取原始指针
      • 2.4指定删除器
      • 2.5 注意事项
      • 2.6 共享指针的计数规则
        • 2.6.1计数增加
        • 2.6.2计数减少
        • 2.6.3 易错点
      • 2.7 shared_ptr线程安全问题
    • 三、智能指针weak_ptr
      • 3.1 weak_ptr的基本使用
      • 3.2 weak_ptr解决循环引用的问题
    • 四、智能指针unique_ptr

C++多线程与共享指针

一、C++多线程使用

  • 多线程应注意线程之间的数据传递,以及数据的共享问题,对共享数据操作的时候应使用锁来解决。
  • 经验:采用初始化时创建线程,然后使用队列网线程中传数据,效率会比需要使用线程时,创建,用完释放的效率低。

1.1 thread

  • 该头文件包含有std::thread类与std::this_thread类。以及管理线程的函数。是实现线程的主要文件。
  • 该头文件包含有std::atomic和std::atomic_flag类,是实现原子操作的的主要文件。
  • 包含互斥相关的类与函数。
  • 包含有future类及相关的函数。
  • 包含有条件变量的类。
#include
#include

std::thread t(func);          //无参数函数
std::thread t(func, parameter)     //带参数函数
std::thread t(&class::func, this, parameter)     //类内函数新起线程   第一个参数类函数指针,第二个参数对象指针

//如果要参数要被改变。采用std::ref
t.joinable();   
t.join();
t.detach();
//创建线程后,可选用join和detach
//joinable:join或detach一次,多次返回false
//detach方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束。detach将线程与主线程剥离,不再受主线程管理。
//注意:在释放各个对象时,请确保detach的线程已完成
//join方式,等待启动的线程完成,才会继续往下执行。join的意思为进入到线程里

//注意:线程的启动与否与join和detach无关,创建线程后,线程已经启动开始运行

std::thread t3(move(t1));       //将线程从t1转移给t3
t1.swap(t2);     //交换线程

std::this_thread::sleep_for(chrono::milliseconds(10));     //线程等待

t.get_id()//获取线程id
auto mainThreadId = std::this_thread::get_id();      //在当前线程获取id

//std::mutext:   独占的互斥量,不能递归使用

//std::timed_mutex:   带超时的互斥量,不能递归使用。try_lock_for

//std::recursive_mutex:  递归互斥量,不带超时功能。

//std::recursive_timed_mutex:  带超时的递归互斥量

std::mutex g_lock; 
g_lock.lock();           //锁
g_lock.unlock();        //去锁
//互斥锁:
//lock() :第一个线程进去后,后面的线程会进入阻塞休眠状态,被放入到阻塞队列中。
//unlock():加锁的线程执行完后,会解锁,然后去阻塞队列中唤醒阻塞线程
//适用于锁的粒度大的场景
//自旋锁:
//lock():第一个线程进去,后面的线程在循环空转检查。
//unlock():第一个加锁的线程解锁,后面的线程检测到就可以被cpu调度。

std::lock_guard<std::mutex> lock(mutex);       //获取互斥量,创建时,自动锁, 析构时,自动释放
std::unique_lock<std::mutex> lock(mutex, std::defer_lock); //比lock_guard功能更强,也具有自动创建锁的功能,但若指定为defer_lock,则不自动创建锁

std::recursive_mutex mutex;
std::lock_guard<std::recursive_mutex> lock(mutex);   // 获取互斥量

t1.native_handle();      //返回 native handle(由于 std::thread 的实现和操作系统相关,因此该函数返回与 std::thread 具体实现相关的线程句柄,

t1.hardware_concurrency(); // 检测硬件并发特性,返回当前平台的线程实现所支持的线程并发数目,但返回值仅仅只作为系统提示(hint)

std::this_thread::yield();  //当前线程放弃执行,操作系统调度另一线程继续执行。

sleep_until();// 线程休眠至某个指定的时刻(time point),该线程才被重新唤醒。

1.2 pthread

#include 

// 定义线程的 id 变量,多个变量使用数组
std::pthread_t tids[NUM_THREADS];


std::pthread_create(thread, attr, start_routine, arg);
int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
//参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
//thread	指向线程标识符指针。
//attr	一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。
//start_routine	线程运行函数起始地址,一旦线程被创建就会执行。
//arg	运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。

std::pthread_exit(NULL);    //终止线程

std::pthread_join(threadid, status)     //连接线程,子程序阻碍调用程序,直到指定的 threadid 线程终止为止。

std::pthread_detach(threadid);      //分离线程

std::pthread_attr_t attr;    //定义线程属性
// 初始化并设置线程为可连接的(joinable)
std::pthread_attr_init(&attr);    
std::pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

// 删除属性
std::pthread_attr_destroy(&attr);

1.3 多线程管理

  • join和detach的作用:

1)线程创建时,就已开始运行。

2)detach分离线程,后台运行,主线程不再对该线程有控制权限,

3)join,跳转进入到子线程当中,线程会阻塞到当前,线程运行结束才会继续执行主线程后续的程序

  • join例子:
#include 
#include 
#include 

void func(int* count){
    *count = 0;
    while(1){
        std::this_thread::sleep_for(std::chrono::seconds(1));
        *count += 1;
        std::cout << "count = " << *count << std::endl;
        if (*count > 10) break;
    }
}

int main(){
    int* count = new int[1];
    std::thread* t_a = new std::thread();
    std::thread t(func, std::ref(count));
    std::cout << "t_a.id = " << t_a->get_id() << std::endl;
    std::cout << "t.id = " << t.get_id() << std::endl;
    std::cout << "t_a.native_handle = " << t_a->native_handle() << std::endl;
    std::cout << "t.native_handle = " << t.native_handle() << std::endl;
    t_a->swap(t);            //交换两个线程
    if(t_a->joinable()) t_a->join();
    std::cout << "t_a.id = " << t_a->get_id() << std::endl;
    std::cout << "t.id = " << t.get_id() << std::endl;
    std::cout << "t_a.native_handle = " << t_a->native_handle() << std::endl;
    std::cout << "t.native_handle = " << t.native_handle() << std::endl;
	delete[] count;
    return 0;
}

/*
t_a.id = thread::id of a non-executing thread
t.id = 2
t_a.native_handle = 0
t.native_handle = 2
count = 1
count = 2
count = 3
count = 4
count = 5
count = 6
count = 7
count = 8
count = 9
count = 10
count = 11
t_a.id = thread::id of a non-executing thread
t.id = thread::id of a non-executing thread
t_a.native_handle = 0
t.native_handle = 0
*/

分析:
join阻塞,执行完后才会执行之后的代码
new的线程没有句柄和相应的id
线程结束后,id都没有,但是句柄都不存在

  • detach
#include 
#include 
#include 

void func(int* count){
    *count = 0;
    while(1){
        std::this_thread::sleep_for(std::chrono::seconds(1));
        *count += 1;
        std::cout << "count = " << *count << std::endl;
        if (*count > 10) break;
    }
}

int main(){
    int* count = new int[1];
    std::thread* t_a = new std::thread();
    std::thread t(func, std::ref(count));
    std::cout << "t_a.id = " << t_a->get_id() << std::endl;
    std::cout << "t.id = " << t.get_id() << std::endl;
    std::cout << "t_a.native_handle = " << t_a->native_handle() << std::endl;
    std::cout << "t.native_handle = " << t.native_handle() << std::endl;
    t_a->swap(t);            //交换两个线程
    if(t_a->joinable()) t_a->detach();
    std::cout << "t_a.id = " << t_a->get_id() << std::endl;
    std::cout << "t.id = " << t.get_id() << std::endl;
    std::cout << "t_a.native_handle = " << t_a->native_handle() << std::endl;
    std::cout << "t.native_handle = " << t.native_handle() << std::endl;
    while(1){
        std::this_thread::sleep_for(std::chrono::seconds(5));
        break;
    }
    delete[] count;
    return 0;
}
/*
t_a.id = thread::id of a non-executing thread
t.id = 2
t_a.native_handle = 0
t.native_handle = 2
t_a.id = thread::id of a non-executing thread
t.id = thread::id of a non-executing thread
t_a.native_handle = 0
t.native_handle = 0
count = 1
count = 2
count = 3
count = 4
*/

分析:
detach阻塞,线程分离,直接执行后续程序
需要注意:
​ 1)程序本身并没有正常退出,而是删除了导致的异常退出。因此,detach需要注意子线程是否完成。
​ 2)如果在线程结束前,delete了count,也会异常退出。
​ 3)即使删除了t_a线程,也不能终止线程

  • 因此在析构时,需要确保线程已经结束,可以封装一个新的线程类
//使用RAII等待线程完成
class thread_guard{
    std::thread& t;
public:
    explicit thread_guard(std::thread& t_): t(t_){}
    ~thread_guard()
    {
        if(t.joinable()){
            t.join();
        }
    }
    thread_guard(thread_guard const&)=delete;
    thread_guard& operate=(thread_guard const&)=delete;
};
struct func;
void f()
{
    int some_local_state = 0;
    func my_func(some_local_state);
    std::thread t(my_func);
    thread_guard g(t);
}

1.4 多线程终止问题

终止的方式分为:

  • return的方式(建议),这种退出线程的方式是最安全的,在线程函数return返回后, 会清理函数内申请的类对象, 即调用这些对象的析构函数。然后会自动调用 _endthreadex()函数来清理 _beginthreadex()函数申请的资源。
  • 同一个进程或另一个进程中的线程调用TerminateThread函数(应避免使用该方法)。TerminateThread能够撤消任何线程,其中hThread参数用于标识被终止运行的线程的句柄。当线程终止运行时,它的退出代码成为你作为dwExitCode参数传递的值。同时,线程的内核对象的使用计数也被递减。注意TerminateThread函数是异步运行的函数,也就是说,它告诉系统你想要线程终止运行,但是,当函数返回时,不能保证线程被撤消。如果需要确切地知道该线程已经终止运行,必须调用WaitForSingleObject或者类似的函数,传递线程的句柄。
  • 通过调用ExitThread函数,线程将自行撤消(最好不使用该方法)。该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++资源(如C++类对象)将不被析构。
  • ExitProcess和TerminateProcess函数也可以用来终止线程的运行(应避免使用该方法)

现有的线程结束函数,包括linux系统的pthread.h中的pthread_exit()pthread_cancel(),windows系统的win32.h中的ExitThread()TerminateThread(),也就是说,C++没有提供kill掉某个线程的能力,只能被动地等待某个线程的自然结束,析构函数~thread()也不能停止线程,析构函数只能在线程静止时终止线程joinable,对于连接/分离的线程,析构函数根本无法终止线程。

linux可以使用pthread_cancel的方式来结束线程,windows使用TerminateThread结束没有成功,而ExitThread需要在线程内执行,才能结束线程

#include 
#include 
#include 
#include 
#include 
#include 
#include 

class Foo {
public:
    void sleep_for(const std::string &tname, int num)
    {
        int count = 0;
        while(1){
            std::this_thread::sleep_for(std::chrono::seconds(1));
            count += 1;
            std::cout << tname +  " count = " << count << std::endl;
            if (count > num) break;
        }
    }

    void start_thread(const std::string &tname)
    {
        std::thread thrd = std::thread(&Foo::sleep_for, this, tname, 5);
        tm_[tname] = thrd.native_handle();
        thrd.detach();
        std::cout << "Thread " << tname << " created:" << std::endl;
    }

    void stop_thread(const std::string &tname)
    {
        ThreadMap::const_iterator it = tm_.find(tname);
        if (it != tm_.end()) {
            pthread_cancel(it->second);
            tm_.erase(tname);
            std::cout << "Thread " << tname << " killed:" << std::endl;
        }
    }

private:
    typedef std::unordered_map<std::string, pthread_t> ThreadMap;
    ThreadMap tm_;
};

int main()
{
    Foo foo;
    std::string keyword("test_thread");
    std::string tname1 = keyword + "1";
    std::string tname2 = keyword + "2";

    foo.start_thread(tname1);
    foo.start_thread(tname2);
    while(1){
        std::this_thread::sleep_for(std::chrono::seconds(2));
        break;
    }
    foo.stop_thread(tname1);
    foo.stop_thread(tname2);
    std::cout << "has no exception" << std::endl;

    return 0;
}
/*
Thread test_thread1 created:
Thread test_thread2 created:
test_thread1 count = 1
test_thread2 count = 1
test_thread2 count = 2
test_thread1 count = 2
Thread test_thread1 killed:
Thread test_thread2 killed:
has no exception
*/

线程运行一段时间后,被取消。

二、共享指针shared_ptr

  • 智能指针是存储指向动态分配对象指针的类。三种智能指针:
    std::shared_ptr/std::unique_ptr/std::weak_ptr.
  • 使用时需#include
  • 不用自己手动释放空间,其内部有一个计数器。

2.1 初始化

std::shared_ptr<int> p(new int(1));
std::shared_ptr<int> p2 = p;    //p和p2共享同一段内存, 如果p2申请了空间,也会释放p2,p的次数加1

std::shared_ptr<int> ptr;
ptr.reset(new int(1));

int *p1 = new int[2];
std::shared_ptr<int> p3(p1); //p1和p3共享同一段内存

//使用make_shared.  更高效
auto p1 = std::make_shared<int>(10);
auto p2 = std::make_shared<string>(10,"s");
auto p3 = std::make_shared<Struct>();

2.2 make_shared和new+shared_ptr的区别

std::shared_ptr<int> ptr(new int(1));  //方式1

std::shared_ptr<int> ptr = std::make_shared<int>()  //方式2

//方式1:std::shared_ptr构造函数会执行两次内存申请,两次空间申请均在堆上,且不连续。
//而方式2:std::make_shared则执行一次,申请一次空间。只申请一次,堆空间连续。

2.3 获取原始指针

std::shared_ptr<int> ptr(new int(1));
int *p = ptr.get();

2.4指定删除器

void deleteIntPtr(int* p)
    {
        delete p;
    }
std::shared_ptr<int> p5(new int,deleteIntPtr);

//使用lambda表达式
std::shared_ptr<int> p6(new int,[](int* p){delete p;});
std::shared_ptr<int> p6(new int[10],[](int* p){delete[] p;});    //数组
std::shared_ptr<int> p7(new int[10],std::default_delete<int[]>);   //使用default_delete作为删除器

//封装,使得共享指针支持共享数组
template<typename T>
std::shared_ptr<T> make_shared_array(size_t size)
{
    return std::shared_ptr<T>(new T[size],std::default_delete<T[]>());
}
std::shared_ptr<int> p8 = make_shared_array<int>(10);
std::shared_ptr<char> p9 = make_shared_array<char>(10);

2.5 注意事项

  • 不能使用原始指针初始化多个shared_ptr
int* p11 = new int;
std::shared_ptr<int> p12(p11);
std::shared_ptr<int> p13(p11);
  • 不要在实参中创建shared_ptr,应该先创建一个智能指针,再使用
deleteIntPtr(std::shared_ptr<int>(new int));//错误的
std::shared_ptr<int> p14(new int());
deleteIntPtr(p14);//OK
  • 要通过shared_from_this()返回this指针
struct A
{
    std::shared_ptr<A> getSelf()
    {
        return std::shared_ptr<A>(this);    //错误,
    }
};
int main()
{
    std::shared_ptr<A> sp1(new A);
    std::shared_ptr<A> sp2 = sp1->getSelf();//会导致重复析构
   return 0;
}


// 正确使用方式
class A:public std::enable_shared_from_this
{
public:
    std::shared_ptr<A> getSelf()
    {
        return shared_from_this();
    }
};
  • 免循环使用,如下A/B两个指针都不会被删除会有内存泄漏(可用weak_ptr解决)
struct A;
struct B;
struct A
{
    std::shared_ptr<B> bptr;
    ~A(){cout << "A is deleted!"<<endl;}
};
struct B
{
    std::shared_ptr<A> aptr;
    ~B() {cout << "B is deleted!"<<endl;}
};
int main()
{
    {
        std::shared_ptr<A> ap(new A);
        std::shared_ptr<B> bp(new B);
        ap->bptr = bp;
        bp->aptr = ap;
     
     }
}
  • 避免使用栈上的指针(局部变量),使用new申请的堆上的指针。

2.6 共享指针的计数规则

  • 每个共享指针都有一个引用计数,记录着有多少指针指向自己。
  • shared_ptr的析构函数:递减它所指向的对象的引用计数,如果引用计数变为0,就会销毁对象并释放相应的内存。
  • 引用计数的变化:决定权在shared_ptr,而与对象本身无关。
2.6.1计数增加

sp.use_count()返回对象sp的引用计数。

shared_ptr<int> sp;                       //空智能指针
shared_ptr<int> sp2 = make_shared<int>(3);
shared_ptr<int> sp3(sp2);
cout << sp.use_count() << endl;         //输出0
cout << sp2.use_count() << endl;        //输出2

拷贝一个shared_ptr,其所指对象的引用计数会递增加

  • 用一个shared_ptr初始化另一个shared_ptr
  • 用一个shared_ptr给另一个shared_ptr赋值
  • 将shared_ptr作为参数传递给一个函数
  • shared_ptr作为函数的返回值
2.6.2计数减少
  • 给shared_ptr赋予一个新值
  • shared_ptr被销毁(如离开作用域)
  • sp.reset() 将计数减1。
  • p.reset(new int(1))
    指针与指向原来对象对应的引用计数减1,若引用计数减为0则释放该对象内存;
    指针与指向新对象对应的引用计数加1;
2.6.3 易错点

多个局部shared_ptr指向该对象,那么函数结束时对象的引用计数就不应该只减1。例如:

shared_ptr<int> init()
{
    shared_ptr<int> sp2 = make_shared<int>(3);
    shared_ptr<int> sp3(sp2);
    cout << sp2.use_count() << endl;        //输出2
    return sp2;                             //返回sp2,故引用计数递增,变为3(返回的copy的一个shared_ptr指针,sp2和sp3都会被销毁)
}                                           //sp2和sp3离开作用域,引用计数减2,变为1
 
int main()
{
    auto p = init();                        //此处赋值的拷贝与return处的拷贝是一致的
    cout << p.use_count() << endl;          //输出1
    return 0;
}

2.7 shared_ptr线程安全问题

  • 同一个shared_ptr被多个线程读,是线程安全的;
  • 同一个shared_ptr被多个线程写,不是线程安全的;
  • 共享引用计数的不同的shared_ptr被多个线程写,是线程安全的。
    对于线程中传入的外部shared_ptr对象,在线程内部进行一次新的构造,例如: sharedptr AObjTmp = outerSharedptrObj;
    可参考
    线程安全

三、智能指针weak_ptr

  • weak_ptr用于解决“引用计数”模型循环依赖问题
  • weak_ptr的使用不会引起计数的增加。
  • weak_ptr不影响对象的生命周期。

不影响计数与生命周期

//default consstructor
weak_ptr<string> wp;
{
    shared_ptr<string> p = make_shared<string>("hello world!\n");
    //weak_ptr对象也绑定到shared_ptr所指向的对象。
    wp = p;   
    cout << "use_count: " <<wp.use_count() << endl;
}
//wp是弱类型的智能指针,不影响所指向对象的生命周期,
//这里p已经析构,其所指的对象也析构了,因此输出是0
cout << "use_count: " << wp.use_count() << endl;
use_count: 1
use_count: 0

3.1 weak_ptr的基本使用

  • weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象.
  • expired 用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false.
  • lock 用于获取所管理的对象的强引用(shared_ptr). 如果 expired 为 true, 返回一个空的 shared_ptr; 否则返回一个 shared_ptr, 其内部对象指向与 weak_ptr 相同.
  • use_count 返回与 shared_ptr 共享的对象的引用计数.
  • reset 将 weak_ptr 置空.
  • weak_ptr 支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数.

例子:

void test_valid(weak_ptr<string> &wp)
{
    if(shared_ptr<string>  smptr2 = wp.lock())
    {
        cout << "the shared_ptr is valid\n";
    }
    else
    {
        cout << "the shared_ptr is not valid\n";
    }
    //检查被引用的对象是否已删除 false 仍存在  true 释放
    if(!wp.expired())
    {
        //it is getting valid shared_ptr obj now;
        shared_ptr<string>  smptr1 = wp.lock();
        cout << "   get obj value: " << *smptr1;
    }
}

int main()
{    
    shared_ptr<string> p = make_shared<string>("hello world!\n");

    //default consstructor
    weak_ptr<string> wp1;
    //copy constructor
    weak_ptr<string> wp2(p);
    //assign constructor
    weak_ptr<string> wp3 = wp2;
    
    test_valid(wp2);
    //释放被管理对象的所有权, 调用后 *this 不管理对象
    wp2.reset();
    test_valid(wp2);
    return 0;
}

3.2 weak_ptr解决循环引用的问题

  • 循环引用
class Parent
{
public:
    shared_ptr<Child> child;
};
class Child
{
public:
    shared_ptr<Parent> parent;
};
shared_ptr<Parent> pA(new Parent);
shared_ptr<Child> pB(new Child);
pA->child = pB;
pB->parent = pA;
  • weak_ptr指向shared_ptr指针指向的对象的内存,却并不拥有该内存。
  • 但是,使用weak_ptr成员lock,则可返回其指向内存的一个shared_ptr对象,且在所指对象内存已经无效时,返回指针空值(nullptr)。由于weak_ptr是指向shared_ptr所指向的内存的,所以,weak_ptr并不能独立存在。
  • 使用weak_ptr查看共享指针是否失效,来判断是否有内存泄漏
include <iostream>
#include 
using namespace std;

void Check(weak_ptr<int> &wp)
{
    shared_ptr<int> sp = wp.lock(); // 重新获得shared_ptr对象
    if (sp != nullptr)
    {
        cout << "The value is " << *sp << endl;
    }
    else
    {
        cout << "Pointer is invalid." << endl;
    }
}

int main()
{
    shared_ptr<int> sp1(new int(10));
    shared_ptr<int> sp2 = sp1;
    weak_ptr<int> wp = sp1; // 指向sp1所指向的内存

    cout << *sp1 << endl;
    cout << *sp2 << endl;
    Check(wp);

    sp1.reset();
    cout << *sp2 << endl;
    Check(wp);

    sp2.reset();
    Check(wp);

    system("pause");
    return 0;
}

四、智能指针unique_ptr

  • 用于防止内存泄漏的智能指针
  • 独享被管理对象指针所有权的智能指针,关联原始指针的唯一所有者,因此不能复制unique_ptr指针。因此也没有引用计数。
  • unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。
  • unique_ptr具有->和*运算符重载符,因此它可以像普通指针一样使用。
//创建一个空的unique_ptr的对象
std::unique_ptr<int> ptr1;

//检查unique_ptr是否为空
// 方法1
if(!ptr1)
	std::cout<<"ptr1 is empty"<<std::endl;
// 方法2
if(ptr1 == nullptr)
	std::cout<<"ptr1 is empty"<<std::endl;

//使用原始指针创建unique_ptr
std::unique_ptr<Task> taskPtr(new Task(22));

//使用make_unique创建
std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34);

//获取被管理对象的指针
Task *p1 = taskPtr.get();

//重置,它将释放delete关联的原始指针并使unique_ptr 对象为空。
taskPtr.reset();

//unique_ptr不能复制

// 编译错误 : unique_ptr 不能复制
std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error
// 编译错误 : unique_ptr 不能复制
taskPtr = taskPtr2; //compile error

//转移 unique_ptr 对象的所有权,转移后,taskPtr2为空
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);

//释放原始指针所有权,返回原始指针。释放所有权,并没有delete原始指针,reset()会delete原始指针。
Task * ptr = taskPtr5.release();

你可能感兴趣的:(C++学习,c++)