Effective C++ 条款14 在资源管理类中小心 copying 行为

条款14 : 在资源管理类中小心 copying 行为

本条款是在我们自己建立资源管理类时要注意的行为, 但是归根结底, 我们为什么要自己建立资源管理类呢 ? 为什么不用 shared_ptr ? 这是我们在本条款中需要首先解决迭代问题.

书中提出, C++提供的智能指针是适配于heap-based资源上的, 其要求不管是自动生成还是手动完成, 该资源必须要有析构函数, 然而并非所有的资源都是heap-based的, 简单来说就是有些资源没有对应的析构函数, 而是选择了别的方式进行资源的释放, 非常典型的就是文件句柄, 其必须要调用close()函数释放, 你如果直接把其交给shared_ptr而不做其他动作, 可以确定的是shared_ptr并不会智能到把close()加到智能函数中, 文件句柄不会被释放, 这样的资源还有很多, 而且大部分都很关键, 比如锁, 数据库连接, 网络socket等. 因此我们需要自己建立自己的资源管理类(当然也有些其他的方式).


怎么建立自己的资源管理类?

简单来说还是遵循RAII原则, 构造即初始化, 析构即释放资源, 这里的释放资源具体到文件就是调用close(), 具体到锁就是调用unlock(), 我们自己应当考量, 而我们一般称其为 RAII风格的XXX .

书中给出了一段代码, 用于实现RAII风格的锁 :

1
2
3
4
5
6
7
8
9
10
11
class Lock {
public:
explicit Lock(Mutex *pm)
: mutexPtr(pm)
{ lock(mutexPtr); } // 获得资源

~Lock() { unlock(mutexPtr); } // 释放资源

private:
Mutex *mutexPtr;
};

于是我们就可以实现以下代码 :

1
2
3
4
5
6
Mutex m;                    // 设置一个互斥器
...
{ // 这是一块作用域, 可以是一个要求线程安全的函数内部
Lock ml(&m); // 直接用互斥器上锁
... // 执行对锁有需求的行为
} // 离开作用域自动调用析构, 析构中自动unlock

言归正传, 当一个RAII对象被复制, 会发生什么事 ?

以下是可能发生的复制策略 :

  • 禁止复制 : 很多时候我们并不希望资源管理类可以复制, 就像锁, 我们一定不希望多个锁对象控制同一个底层的互斥器, 这有违锁设计的初衷, 所以直接禁止就好了, 这时我们的Uncopyable类就有用了 :

    1
    2
    3
    4
    class Lock: private Uncopyable {        // 直接private继承自Uncopyable
    public:
    ... // 同上
    };
  • 对底层资源祭出”引用计数法” : 这个其实就是利用shared_ptr实现资源共享就好了.

    需要注意的就是, shared_ptr还有一个和共享内存无关的性质—删除器, 这是一个函数, 可以传入shared_ptr构造函数的第二参数, 如果没有删除器会默认调用析构, 有删除器就调用删除器, 这其实就在一定程度上解决了智能指针只能针对heap-based资源的问题, 让没有析构函数的资源也可以通过调用删除器实现释放, 以下是书中的代码 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Lock {
    public:
    explicit Lock(Mutex *pm)
    : mutexPtr(pm, unlock) // 初始化智能指针, 将unlock设置为删除器
    lock(mutexPtr.get());
    }

    void unlock(Mutex* mtx) {
    if (mtx) {
    mtx->unlock();
    }

    // 不需要写析构函数了, 它可以被删除器替代
    }

    private:
    std::tr1::shared_ptr<Mutex> mutexPtr; // 使用 shared_ptr
    };

    请注意如果我们写的资源管理类不希望共享资源, shared_ptr可以共享就带来了隐患, 像是上面的锁, 其实更推荐禁止拷贝的做法, 这种做法只是告诉我们一种其他的做法而已.

  • 复制底层资源 : 这其实就是我们常说的深拷贝.

  • 转移底部资源的拥有权 : 为了保证资源的独占性, 我们可以选择这种策略.


请记住 :

  • 复制RAII对象必须一并复制他所管理的资源, 资源的copying行为决定RAII对象copying行为.
  • 普遍的copying行为是禁止复制或施行引用计数法.
  • 文件句柄和锁这类资源可以选择禁止复制, 支持移动的策略

by 天目中云


Effective C++ 条款14 在资源管理类中小心 copying 行为
http://example.com/2024/11/30/[Effective C++]条款14 在资源管理类中小心 copying 行为/
作者
天目中云
发布于
2024年11月30日
许可协议