委员长 发表于 7 天前

C++Primer学习笔记13.智能指针和异常

#### 智能指针和异常

使用异常处理的程序能在异常发生后令程序流程继续,它需要确保在异常发生后资源能被正确地释放,一种简单的方法是使用智能指针。

使用智能指针时发生异常,智能指针管理的内存会被释放掉,而如果是直接管理内存时,在 new 和 delete 之间发生了异常,则内存不会被释放。

> <b>智能指针和哑类</b>

所有标准库类都定义了析构函数,负责清理对象使用的资源。

但是那些为 C 和 C++ 两种语言设计的类,通常都没有良好定义的析构函数,必须显式释放资源。

如果在资源分配和释放之间发生了异常,或程序员忘记释放资源,程序也会发生资源泄漏。

例如网络连接中的在释放连接前发生了异常,那么连接就不会被释放了。

> <b>使用自己的释放操作</b>

默认情况下,shared_ptr 假定它们指向的是动态内存,在销毁时会使用 delete 操作。

但也可以使用 shared_ptr 管理其他对象,如网络连接,这时就需要定义一个相应的删除器函数来代替 delete。

可以定义一个函数来代替 delete,称为删除器。

```cpp
// deleter 必须是一个接受一个 T* 类型参数的函数
share_ptr<T> p(&t, deleter);
// 使用 shared_ptr 管理网络连接
// end_connection 是 p 的删除器,它接受一个 connection* 参数
shared_ptr<connection> p(&c, end_connection);      
```

> <b>智能指针陷阱</b>

- <span style="color:red">不使用相同的内置指针值初始化或 reset 多个智能指针</span>
- 不 delete get() 返回的指针
- 不使用 get() 初始化或 reset 另一个智能指针
- 如果使用 get() 返回的指针,当最后一个对应的智能指针被销毁后,指针就变为无效的了
- <span style="color:red">如果智能指针管理的不是 new 分配的内存,记住传递给它一个删除器</span>

#### unique_ptr

同一时刻只能有一个 unique_ptr 指向一个给定对象。当 unique_ptr 被销毁时,指向对象也被销毁。

unique_ptr 没有类似 make_shared 的标准库函数返回一个 unique_ptr。定义一个 unique_ptr 时,需要绑定到一个 new 返回的指针上(不同于 shared_ptr)。c++14 中加入了 make_unique

类似 shared_ptr,初始化 unique_ptr 必须采用直接初始化。(这里指使用 new 初始化)

```cpp
unique_ptr<double> p1; // 可以指向一个 double 的 unique_ptr
unique_ptr<int> p2(new int(42)); // p2 指向一个值为 42 的 int
```

因为 unique_ptr 独有它指向的对象,所有它不支持拷贝和赋值操作。实际上 unique_ptr 的拷贝构造函数被定义为删除的。

```cpp
unique_ptr<string> p1(new string("aaa"));
unique_ptr<string> p2(p1); // 错误,unique_ptr 不支持拷贝
unique_ptr<string> p3;
p3 = p2; // 错误,unique_ptr 不支持赋值
```

unique_ptr 定义和初始化

```cpp
unique_ptr<int> u1;             // 定义一个空 unique_ptr
unique_ptr<int> u1(new int());// 正确
unique_ptr<int,deleter> u;      // 定义一个空 unqiue,用可调用对象 deleter 代替 delete
unique_ptr<int,deleter> u(d);   // 空 unique,用类型为 deleter 的对象 d 代替delete
unique_ptr<int,decltype(d)*> u(new int(42),d);

unique_ptr<int> u2(u1);         // 错误:不支持拷贝      
```

<span style="color:red">注意:unique_ptr 管理删除器的方式与 shared_ptr 不一样。unique_ptr 将删除器放在尖括号中。</span>

unique_ptr 操作

```cpp
u.get();
u1.swap(u2);swap(u1,u2);

u = nullptr;             // 释放 u 指向的对象并将 u 置为空
auto u2 = u.release();   // u 放弃对指针的控制权,返回 u 中保存的内置指针,并将 u 置为空,注意 u2 的类型是内置指针,而不是 unique_ptr
u.reset();               // 释放 u 指向的对象
u.reset(nullptr);      // 释放 u 指向的对象,并将 u 置为空,等价于 u = nullptr;
u.reset(q);            // 令 u 指向内置指针 q 指向的对象
```

可以通过 release 或 reset 将指针的所有权从一个 unique_ptr 转移给另一个 unique

```cpp
// 控制器转移给 u2,u1 置为空
unique_ptr<int> u2(u1.release());
// 释放 u3 原来指向的内存,u3 接管 u1 指向的对象。
u3.reset(u1.release());
```

release 的使用

release 返回的指针通常用来初始化其他智能指针或给其他智能指针赋值。release 返回的指针不能空闲,必须有其他指针接管对象。如果是一个内置指针接管了 release 返回的指针,那么程序就要负责资源的释放。

```cpp
// 错误:release 不会释放内存,没有其他指针接管内存。
u.release();
// 正确,但必须记得 delete p
auto u2 = u1.release();
```

> <b>传递 unique_ptr 参数和返回 unique_ptr</b>

不能拷贝 unique_ptr 参数的规则有一个例外:可以拷贝或赋值一个将要被销毁的 unique_ptr。如从函数返回一个 unique_ptr

```cpp
unique_ptr<int> clone(int p) {
    unique_ptr<int> ret(new int(p));
    return ret;
}
```

上面这种情况,编译器知道要返回的对象将要被销毁,在此情况下,编译器执行一种特殊的拷贝。

> <b>向 unique_ptr 传递删除器</b>

类似 shared_ptr,unique_ptr 默认情况下使用 delete 释放它指向的对象。可以重载 unique_ptr 中默认的删除器。

但 unique_ptr 管理删除器的方式与 shared_ptr 不一样。unique_ptr 将删除器放在尖括号中。因为对于 unique_ptr 来说,删除器的类型是构成 unique_ptr 类型的一部分。

auto_ptr 是标准库的较早版本包含的一个类,它具有 unique_ptr 的部分特性。相比于 unique_ptr,不能在容器中保存 auto_ptr,也不能从函数返回 auto_ptr。

错误案例

```cpp
int ix = 1024, *pi = &ix, *pi2 = new int(2048);
unique_ptr<int> p0(ix);      // 错误:从 int 到 unique_ptr<int> 的无效的转换
unique_ptr<int> p1(pi);      // 运行时错误:当 p1 被销毁时会对 pi 调用 delete,这是一个对非动态分配返回的指针调用 delete 的错误。
unique_ptr<int> p2(pi2);   // 不会报错,但当 p2 被销毁后会使 pi2 成为一个悬空指针
unique_ptr<int> p3(new int(2048));   // 正确,推荐的用法
```

#### weak_ptr

weak_ptr 指向一个由 shared_ptr 管理的对象,它不控制所指向对象的生存期。将一个 weak_ptr 绑定到 shared_ptr 不会改变 shared_ptr 的引用计数。

如果 shared_ptr 都被销毁,即使还有 weak_ptr 指向对象,对象依然会释放(因此不能使用 weak_ptr 直接访问对象)。

> <b>weak_ptr 初始化</b>

创建 weak_ptr 时,要用 shared_ptr 来初始化它。

```cpp
// 默认初始化,定义一个空 weak_ptr w,w 可以指向类型为 T 的对象
weak_ptr<T> w;
// p 可以是一个 shared_ptr 或 weak_ptr,赋值后 w 与 p 共享对象
w = p;
// 定义一个与 shared_ptr sp 指向相同对象的 weak_ptr。
// T 必须能转换成 sp 指向的类型(不必相同)
weak_ptr<T> w(sp);
```

> <b>weak_ptr 操作</b>

因为 weak_ptr 的对象可能被释放的,因此不能直接访问对象,必须调用 lock()。lock() 检查 weak_ptr 所指的对象是否仍存在,如果存在,返回一个指向共享对象的 shared_ptr。

理解:返回的这个 shared_ptr 会使引用计数加 1。

```cpp
// p 可以是一个 shared_ptr 或 weak_ptr。赋值后 w 与 p 共享对象。
w = p;
// 将 w 置为空
w.reset();
// 返回与 w 共享对象的 shared_ptr 的数量
w.use_count();
// 若 w.use_count() 为 0,返回 true,否则返回 false。expired 是 “过期的” 意思
w.expired();
// 如果 w.expired 为 true,返回一个空 shared_ptr;否则返回一个指向 w 的对象的 shared_ptr
w.lock();   
```

> <b style="color:red">weak_ptr 操作的应用</b>

```cpp
// 如果 np 不为空则条件成立
if(shared_ptr<int> np = wp.lock())
```

> <b>检查指针类 StrBlobPtr</b>

作为 weak_ptr 用途的一个展示,我们将为 StrBlob 类定义一个伴随指针类。我们的指针类将命名为 StrBlobPtr,会保存一个 weak_ptr,指向 StrBlob 的 data 成员,这是初始化时提供给它的。通过使用 weak_ptr,不会影响一个给定的 StrBlob 所指向的 vector 的生存期。但是,可以阻止用户访问一个不再存在的 vector 的企图。

StrBlobPtr 会有两个数据成员:wptr,或者为空,或者指向一个 StrBlob 中的 vector;curr,保存当前对象所表示的元素的下标。类似它的伴随类 StrBlob,我们的指针类也有一个 check 成员来检查解引用 StrBlobPtr 是否安全

```cpp
// 对于访问一个不存在的元素的尝试,StrBlobPtr 抛出一个异常
class StrBlobPtr{
public:
    StrBlobPtr() : curr(0) {}
    StrBlobPtr(StrBlob &a, size_t sz = 0) : 
                    wptr(a.data), curr(sz) {}

    string &deref() const;
    StrBlobPtr &incr();

    bool operator!=(const StrBlobPtr &rhs) const { return this->curr != rhs.curr; }

private:
    shared_ptr<vector<string>> check(std::size_t, const string &msg) const; //不能在 const 成员函数内调用本类的非 const 成员函数,调用的必须也是 const 成员函数

private:
    // 保存一个 weak_ptr 意味着底层 vector 可能会被销毁
    weak_ptr<vector<string>> wptr;
    size_t curr;
};

shared_ptr<vector<string>> StrBlobPtr::check(std::size_t sz, const string &msg) const{
    auto ret = wptr.lock();
    if (!ret) throw std::runtime_error("unbound StrBlobPtr");    //检查 wptr 是否绑定了一个 StrBlob
    if (sz >= ret->size()) throw std::out_of_range("msg");
    return ret;
}

string &StrBlobPtr::deref() const {     //const 成员函数在定义时也要加上 const
    auto p = check(curr, "dereference past end");
    return (*p);
}

StrBlobPtr &StrBlobPtr::incr(){
    check(curr, "increment past end of StrBlobPtr");
    ++curr;
    return *this;
}
```
页: [1]
查看完整版本: C++Primer学习笔记13.智能指针和异常