​std::unique_ptr​​ 

​std::unique_ptr​​ 是一种独占的语义,即只允许一个智能指针引用裸指针,这区别于 ​​std::shared_ptr​​ 允许多个 ​​shared_ptr​​ 引用同一个裸指针,它没有引用计数,它的性能比 ​​shared_ptr​​ 会高一点。

在用法上 ​​std::unique_ptr​​ 和 ​​std::shared_ptr​​ 是类似的,主要的不同是 ​​std::unique_ptr​​ 之间的赋值需要通过 ​​std::move​​ 实现。

在 code2 目录下新建一个 code5.cpp 文件:

#include <iostream>
#include <memory>
#include <functional>

struct A{
~A(){
std::cout<<"destruct A"<<std::endl;
}
};

int main(){

std::unique_ptr<int> p(new int(1));
std::cout<<*p<<std::endl;

{
std::unique_ptr<A> p(new A);
std::unique_ptr<A> p1 = std::move(p);
}

{
std::unique_ptr<A[]> p(new A[3]);
}

{
std::unique_ptr<A, std::function<void(A*)>> p(new A, [](A* ptr){
std::cout << "delete from a custom deleter...\n";
delete ptr;
});
}
}

编译和运行代码:在 build 目录下执行

g++ ../code5.cpp -o code5 -std=c++11 && ./code5

 

输出结果:

1
destruct A
destruct A
destruct A
destruct A
delete from a custom deleter...
destruct A

 

构造 ​​unique_ptr​​ 的时候第二个参数是一个自定义删除器,如果不填写自定义删除器,就会使用默认的删除器,一般情况下我们用默认的删除器就可以了,如果有需要也可以写自定义的删除器。注意 C++11 中构造 ​​std::unique_ptr​​ 不能像 ​​std::shared_ptr​​ 那样(通过 ​​make_shared​​ 创建)通过 ​​make_unique​​ 方式去创建,​​make_unique​​ 在 C++14 中才提供,当然也可以自己实现一个,具体实现这里不再赘述。

std::weak_ptr

std::weak_ptr 是用来监视 std::shared_ptr 的,通过 weak_ptr 就可以得知它监视的 shared_ptr 是否已经销毁了。

在 code2 目录下新建一个 code6.cpp 文件:

#include <iostream>
#include <memory>

std::weak_ptr<int> gw;

void f()
{
if (auto spt = gw.lock()) { // 使用之前必须复制到 shared_ptr
std::cout << *spt << "\n";
}
else {
std::cout << "gw is expired\n";
}
}

int main()
{
{
auto sp = std::make_shared<int>(42);
gw = sp;

f();
}

f();
}

编译和运行代码:在 build 目录下执行

g++ ../code6.cpp -o code6 -std=c++11 && ./code6

 

输出结果:

42
gw is expired

 

​std::weak_ptr​​ 需要通过 ​​std::shared_ptr​​ 构造,当所监视的 ​​shared_ptr​​ 析构之后,通过 ​​weak_ptr​​ 的 ​​lock​​ 方法返回的是一个 ​​nullptr​​ ,如果 ​​shared_ptr​​ 仍然存在则返回 ​​shared_ptr​​ 。

​https://www.shiyanlou.com/courses/1414/learning/?id=14993​

 

通过 shared_ptr 保证安全地回调

​shared_ptr​​ 的一个重要作用就是在回调的时候是安全的,因为回调的时候,该回调函数的宿主对象可能已经被销毁了,那么在后面某个时刻回调的时候,实际上该对象已经不存在了,这时候再执行回调函数就是错误的。通过 ​​shared_ptr​​ 可以解决这个问题,保证安全地回调。

本小结内容可以先无脑动手做一下,到了实验四的时候就可以理解这样做的好处了。

在 code2 目录下新建一个 code7.cpp 文件:

#include <iostream>
#include <memory>
#include <functional>

class person : public std::enable_shared_from_this<person> {

public:
void update(){
name_ = "jack";
callback_();
}

void init_callback(){
callback_ = [this]{
test();
};
}

void test(){
auto self = this->shared_from_this();
std::cout<<name_<<std::endl;
}

private:
std::function<void()> callback_;
std::string name_ = "tom";
};

int main(){
person* ptr = nullptr;

{
std::shared_ptr<person> p = std::make_shared<person>();
p->init_callback();

ptr = p.get();
}

ptr->update();
}

 

编译和运行代码:在 build 目录下执行

g++ ../code7.cpp -o code7 -std=c++11 && ./code7

 

输出结果:

terminate called after throwing an instance of 'std::bad_weak_ptr'
what(): bad_weak_ptr
[1] 209 abort (core dumped) ./code7

会出现一个运行错误,因为在调用 ​​ptr->update()​​ 时,该 ​​ptr​​ 已经被前面的 ​​shared_ptr​​ 释放了,这时候这个 ​​ptr​​ 实际上是无效的。

为了解决这个问题,我们可以通过派生于 ​​std::enable_shared_from_this​​ 来通过对象自身得到 ​​shared_ptr​​ ,然后在设置回调的 ​​lambda​​ 中捕获该 ​​shared_ptr​​ ,这样,​​lambda​​ 表达式中的 ​​shared_ptr​​ 和 ​​lambda​​ 回调函数的生命周期一致了。

在 code2 目录下新建一个 code8.cpp 文件:

#include <iostream>
#include <memory>
#include <functional>

class person : public std::enable_shared_from_this<person> {

public:
void update(){
name_ = "jack";
callback_();
}

void init_callback(){
auto self = this->shared_from_this();
callback_ = [this, self]{
test();
};
}

void test(){
auto self = this->shared_from_this();
std::cout<<name_<<std::endl;
}

private:
std::function<void()> callback_;
std::string name_ = "tom";
};

int main(){
person* ptr = nullptr;

{
std::shared_ptr<person> p = std::make_shared<person>();
p->init_callback();

ptr = p.get();
}

ptr->update();
}

 

编译和运行代码:在 build 目录下执行

g++ ../code8.cpp -o code8 -std=c++11 && ./code8

输出结果:

jack

关键的一句是 ​​lambda​​ 捕获了 ​​shared_ptr​​ ,即使外面的 ​​shared_ptr​​ 重置了,但是 ​​lamdba​​ 表达式中捕获的 ​​shared_ptr​​ 仍然存在,引用计数不会减为 0,可以保证回调回来的时候,该对象仍然存在。

尽量使用std::make_unique和std::make_shared而不直接使用new

让我们从对齐std::make_unique 和 std::make_shared这两块开始。std::make_shared是c++11的一部分,但很可惜std::make_unique不是。它是在c++14里加入标准库的。假如你在使用c++11,也别担心,你很容易写出一个基本的版本。看这里:

template<typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts&&... params)
{
return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}

正如你看到的,make_unique完美传递了参数给对象的构造函数,从一个原始指针构造出一个std::unique_ptr,返回创建的std::unique_ptr。这个形式的函数不支持数组和定制删除器(见条款18),但它证明了一点点的努力就可以根据需要创建一个make_unique。要记住的是不要把你的版本放到std命名空间里,因为你不想当升级到c++14后会和库提供的标准实现冲突吧。