# C++智能指针

## std::unique_ptr
对于独占资源(exclusive-ownership resource)使用std::unique_ptr，它禁止和其他智能指针共享对象

### 使用
与`std::shared_ptr`只有单个对象形式不同, `std::unique_ptr`有两种形式， 对于单个对象(`std::unique_ptr<T>`), 对于数组(`std::unique_ptr<T[]>`), 对于解引用操作符(`operator* `和` operator->`)对于单个对象专有。
```cpp
std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 从 C++14 引入
std::unique_ptr<int> pointer2 = pointer; // 非法
```

C++ 并没有提供 std::make_unique, 可自行实现
```cpp
template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args ) {
  return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}
```

### 类型
对于`std::unique_ptr`来说，删除器是类型的一部分。


### 占用空间
当使用默认删除器(不使用自定义删除器)的时候，`std::unique_ptr`的对象大小和原始指针大小相同，都为一个字(word)。当使用自定义删除器的时候，对于无状态(stateless function)的对象，不捕获变量的lambda表达式(下面的del_func), `std::unique_ptr`大小增加为两个字(word);当捕获外部变量时，std::unique_ptr的大小取决于，函数对象中存储的状态。

```cpp
void func_unique_ptr_size() {
    cout << "ptr size:" << sizeof(new Weight()) << endl;
    unique_ptr<Weight> default_del_unique_ptr(new Weight());
    cout << "default_del_unique_ptr size: " << sizeof (default_del_unique_ptr) << endl;

    auto del_func = [](Weight* w){delete w;};
    unique_ptr<Weight, decltype(del_func)> custom_delete_func_unique_ptr(new Weight(), del_func);
    cout << "custom_delete_func_unique_ptr size: " << sizeof(custom_delete_func_unique_ptr) << endl;

    int i = 100;
    auto del_func_stateful = [&i](Weight* w) {int x = i; delete w;};
    unique_ptr<Weight, decltype(del_func_stateful)> custom_stateful_del_func_unique_ptr(new Weight, del_func_stateful);
    cout << "custom_stateful_del_func_unique_ptr size: " << sizeof(custom_stateful_del_func_unique_ptr) << endl;
}
```
```
输出
ptr size:8
default_del_unique_ptr size: 8
custom_delete_func_unique_ptr size: 8
custom_stateful_del_func_unique_ptr size: 16
```


## std::shared_ptr
 对于共享资源(shared-ownership resource) 使用 `std::shared_ptr`

### 使用
`std::shared_ptr`能够记录有多少个`std::shared_ptr`指向同一个对象，当引用计数为0的时，会自动调用`delete`删除对象。

可以通过`get()`获取原始指针。通过use_count()来查看一个对象的引用计数。当智能指针中有值时，通过`reset()`来减少一个引用计数，调用 `reset(new xxx())`重新赋值，并且引用计数置为1。


### 内部实现
`std::shared_ptr`的大小是原始指针的两倍，包含一个资源的原始指针，和指向控制块(control block)的指针。当使用原始指针创建`std::shared_ptr`时，控制块的内存会动态分配，使用`make_shared`可以避免。

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1657339694834/-pY0DvSTA.png align="left")

当使用自定义删除器时，不会改变`shared_ptr`的大小，因为自定义删除器保存在`shared_ptr`内指针指向的内存。

### 控制块创建的时机

* 使用`make_shared`时总会创建一个控制块
```cpp
shared_ptr<Weight> sp0 = std::make_shared<Weight>()
```
* 从独占指针`unique_ptr`上构造`shared_ptr`时会创建控制快
```cpp
unique_ptr<Weight> uniquePtr(new Weight());
shared_ptr<Weight> sp1 = move(uniquePtr);
cout << uniquePtr << endl; // 0x0
```

* 在原始指针上构造`shared_ptr`会创建控制块。但如果从已经存在控制块的对象上创建`shared_ptr`不会创建新的控制块，可以依赖传递来的智能指针指向控制块。
```
shared_ptr<Monster> spM0(new Monster("m0"));
cout << spM0.use_count() << endl; // 1
shared_ptr<Monster> spM1(spM0); // 此处不创建新的控制块, spM1的指向控制快地址和spM0相同
cout << spM0.use_count() << endl; // 2
```
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1657342210774/yQF-EKtN2.png align="left")

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1657342157328/3jOBkniGp.png align="left")

## 使用注意点
1. 当在一个原始指针上创建多个`shared_ptr`，也就是说由多个具有**不同的控制块**的`shared_ptr`管理同一份资源。多个控制块意味着多个引用计数值，多个引用计数值意味着对象将会被销毁多次（每个引用计数一次）
```cpp
auto pw = new Widget;                           //pw是原始指针
std::shared_ptr<Widget> spw1(pw, loggingDel);   //为*pw创建控制块
std::shared_ptr<Widget> spw2(pw, loggingDel);   //为*pw创建第二个控制块
```
因此，在创建`shared_ptr`时，最好使用`make_shared`, 不过这样无法自定义删除器。必须给`shared_ptr`构造函数原始指针时，直接传递 new 出来的匿名对象。
2. 如果你想创建一个用std::shared_ptr管理的类，这个类能够用this指针安全地创建一个std::shared_ptr，std::enable_shared_from_this就可作为基类的模板类。
```cpp
class Widget: public std::enable_shared_from_this<Widget> {
public:
    …
    void process();
    …
};
```
3. shared_ptr不支持数组对象`std::shared_ptr<T[]>`

## std::weak_ptr
当std::shard_ptr可能悬空(dangle)时使用std::weak_ptr
`weak_ptr`是一个可以不需要手动管理内存，同时不参与资源所有权共享的智能指针。它不是一个可以独立存在的指针，是`shared_ptr`的增强。

### 使用
* 可以在`shared_ptr`上创建`weak_ptr`, 在两个指针上使用`use_count()`获取到的都是指向资源的`shared_ptr`的个数
```cpp
shared_ptr<Weight> sp = make_shared<Weight>();
weak_ptr<Weight> wp0(sp);
cout << sp.use_count() << endl; // 1
weak_ptr<Weight> wp1(sp);
cout << sp.use_count() << endl; // 1
cout << wp1.use_count() << endl; // 1
```
* 可以通过一个原子操作检查`weak_ptr`是否已经过期，如果没有过期就访问指向对象。可以通过在`weak_ptr`上创建`shared_ptr`实现。有两种方式:
```cpp
shared_ptr<Weight> sp1 = wp0.lock();
if (sp1 == nullptr) {
    cout << "dangle" << endl;
}
shared_ptr<Weight> sp2(wp0); // 如果wp0过期, 则抛出std::bad_weak_ptr异常
```
* 打破`std::shared_ptr`环状结构

## 使用 std::make_shared 和 std::make_unique
### 优点(建议使用的场景)
1. 避免类型重复
```cpp
auto upw1(std::make_unique<Widget>());      //使用make函数, 只写Weight一次类型
std::unique_ptr<Widget> upw2(new Widget);   //不使用make函数, 需要写两次
```
2. 异常安全(exception safe)
对于函数`void processWidget(std::shared_ptr<Widget> spw, int priority);`有一个函数`int computePriority();`计算优先级, 下面代码存在潜在的内存泄露。
```cpp
processWidget(std::shared_ptr<Widget>(new Widget),  computePriority());
```
一个函数的实参必须先被计算，这个函数再被调用, `new Widget`必须在`std::shared_ptr`的构造函数被调用前执行，因为new出来的结果作为构造函数的实参，但`computePriority`可能在这之前，之后，或者**之间**执行。
```
执行“new Widget”
执行computePriority // 如果在这里抛出异常，会导致 new Weiget的资源无法释放。
运行std::shared_ptr构造函数
```
3. 效率提升
对于`std::shared_ptr<Widget> spw(new Widget);`需要进行两次内存分配。一次使用`new`分配`Weight`的内存。 一次为`shared_ptr`分配控制块内存。
使用`make_shared`增加内存分配效率，并且潜在的减少了程序的总内存占用。

### 不足(不建议使用的场景)
1. 自定义删除器
```cpp
auto widgetDeleter = [](Widget* pw) { … };
std::shared_ptr<Widget> spw(new Widget, widgetDeleter);
```
2. 使用花括号初始化

## ref
> https://www.cnblogs.com/KillerAery/p/9096558.html
https://juejin.cn/post/6844903993055920141#heading-14
https://heleifz.github.io/14696398760857.html
https://cntransgroup.github.io/EffectiveModernCppChinese/4.SmartPointers/item19.html
https://changkun.de/modern-cpp/zh-cn/05-pointers/
