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

2024-01-01 22:34:50

?你可能会发现,有时候需要创建自己的资源管理类。例如,假设你正在使用一个C API来操作互斥对象,互斥类型提供了lock和unlock函数:

void lock(Mutex* pm); 	// 锁住pm指向的互斥量
void unlock(Mutex* pm); 	// 互斥量解锁
class Lock {
public:
    explicit Lock(Mutex* pm)
        : mutexPtr(pm)
    {
        lock(mutexPtr);
    } // 获取资源
    ~Lock() { unlock(mutexPtr); } // 释放资源
private:
    Mutex* mutexPtr;
};
Mutex m; // 定义你需要使用的互斥量
...
{ // 创建一个区块
    Lock ml(&m); // 锁住互斥量
    ... // 执行操作
} // 离开块时,自动解锁

目前都没什么问题,但如果复制了一个Lock对象,会发生什么呢?

Lock ml1(&m); 		// 锁住 m
Lock ml2(ml1); 	// 将ml1复制到ml2,这里会发生什么?
#include <iostream>
//#include <mutex>
class Mutex {
public:
    bool isLocked = false;
};
void lock(Mutex* pm) 	// 锁住pm指向的互斥量
{
    pm->isLocked = true;
    puts("给互斥量上锁!");
}
void unlock(Mutex* pm) 	// 互斥量解锁
{
    pm->isLocked = false;
    puts("给互斥量开锁!");
}
class Lock {
public:
    explicit Lock(Mutex* pm)
        : mutexPtr(pm)
    {
        lock(mutexPtr);
    } // 获取资源
    ~Lock() { unlock(mutexPtr); } // 释放资源
private:
    Mutex* mutexPtr;
};

Mutex m; // 定义你需要使用的互斥量


int main()
{
    Lock ml1(&m); 	// 锁住 m
    Lock ml2(ml1); 	// 将ml1复制到ml2,这里会发生什么?
}

在这里插入图片描述
这边在析构的时候会进行两次解锁。
复制RAII对象时应该如何处理?大多数时候,你有如下选择:
1、禁止复制:如果允许复制RAII对象没有意义,这时应该禁止它。对于像Lock这样的类来说,这可能是正确的。
2、引用计数底层资源:保留资源,直到使用它的最后一个对象被销毁。std::shared_ptr会调用delete,而我们的Lock类,需要的是解锁。幸运的是,shared_ptr允许指定“删除器”。
3.复制底部资源:有时可以有任意多个资源的副本,需要资源管理类的唯一原因是确保每个副本都能被正确释放。在这种情况下,复制资源管理对象也应该复制它包裹的资源。也就是说,复制资源管理对象时进行“深度拷贝”。
4.转移底部资源的所有权:在极少数情况下,需要确保只有一个RAII对象管理资源,当复制RAII对象时,需要转移资源的所有权。
以下是使用第二种方法解决问题。

class Lock {
public:
    explicit Lock(Mutex* pm)   // 用互斥量和unlock函数初始化shared_ptr
        : mutexPtr(pm, unlock) // 作为删除器
    { 
        lock(mutexPtr.get()); // 获取普通指针
    }
private:
    std::shared_ptr<Mutex> mutexPtr; // 使用shared_ptr代替普通指针
}; 

在这里插入图片描述

  • 复制RAII对象需要复制它管理的资源,因此资源的复制行为决定了RAII对象的复制行为。
  • 常见的RAII类复制行为不允许复制和执行引用计数。

文章来源:https://blog.csdn.net/qq_45526401/article/details/135329035
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。