- 为防止资源泄漏,使用RAII思想构建资源管理类,在构造函数中获取资源,在析构函数中释放资源
- 可以使用智能指针管理资源
struct Payment {
Payment() {
printf("Construct Paymentn");
}
~Payment() {
printf("Deconstruct Paymentn");
}
};
static Payment *getPayment() {
static Payment *instance = new Payment;
return instance;
}
int main() {
Payment *p1 = getPayment();
Payment *p2 = getPayment();
cout << p1 << " " << p2 << endl;
}
上述通过工厂函数获取到对象指针,但是存在问题
- 容易忘记delete
- 容易重复delete同一块地址(可见,这里delete了p1,那么p2呢?)
智能指针对heap-based对象有奇效
- 获得资源后立即放进管理对象内。即RAII:Resource Acquisition Is Initialization(资源获取即初始化)。
- 管理对象使用析构函数来释放资源。即离开作用域后就释放资源
int main() {
shared_ptr ptr(getPayment());
{
auto ptr2 = ptr;
cout << ptr.use_count() << endl;
}
cout << ptr.use_count() << endl;
}
但是要注意避免循环引用,造成资源泄漏
class B;
struct A {
shared_ptr ptr;
~A() {
printf("~An");
}
};
struct B {
shared_ptr ptr;
~B() {
printf("~Bn");
}
};
int main() {
shared_ptr pA(new A);
shared_ptr pB(new B);
pA->ptr = pB;
pB->ptr = pA;
}
// 没有析构函数被调用
解决办法: 将类成员的shared_ptr改用为弱引用指针weak_ptr即可
注意智能指针的默认析构方法是delete智能指针默认的析构方法是delete,所以以下函数可以正确编译,但是不能正确释放。
shared_ptr
解决办法: 自定义释放器
struct A {
static int v;
int selfV;
A() : selfV(v++) {}
~A() {
printf("~A %dn", selfV);
}
};
int A::v = 0;
void deleter(A *a) {
printf("call deleter Bn");
delete[]a;
}
int main() {
A *a = new A[2];
shared_ptr ptr(a, [](A *toD) {
printf("call deleter An");
delete[]toD;
});
shared_ptr p2(new A[2], deleter);
}
在资源管理类中小心copy行为
- 拷贝RAII对象必须考虑其管理的资源,针对其资源做出拷贝行为的实现
- 常见的RAII对象拷贝行为:拒绝拷贝、引用计数法、深拷贝、资源所有权转移
并非所有资源都是基于堆的(heap-based),对于这种对象不能直接使用智能指针,需要自定义其资源管理类。
例子:锁
为了说明锁的资源管理行为,我们这里给定义一个锁,来替代C++里的锁
struct MyMutex {
MyMutex() {
printf("Construct MyMutexn");
}
~MyMutex() {
printf("Deconstruct MyMutexn");
}
};
其上锁解锁行为:
void lock(MyMutex *) {
printf("lockn");
}
void unlock(MyMutex *) {
printf("unlockn");
}
锁的资源管理类,在构造函数获取资源(加锁),在析构函数释放资源(解锁):
struct Lock {
private:
MyMutex *myMutex;
public:
explicit Lock(MyMutex *mutex) : myMutex(mutex) {
lock(myMutex);
}
~Lock() {
unlock(myMutex);
}
};
使用:
int main() {
MyMutex myMutex;
{
printf("---------n");
Lock lk(&myMutex);
printf("---------n");
// 离开代码块将自动析构局部对象,因此会释放锁
}
}
潜在风险,如果发生了拷贝行为:
Lock l1(&mutex); Lock l2(l1);
那么将立即死锁(Linux里一般是非递归锁,重复加锁会造成死锁)
禁止复制继承nocopyable,或者将拷贝相关函数设置为delete
对底层资源使用引用计数法思想:维护一个计数器,当最后一个使用者被销毁时,才真正释放资源
- 维护计数器
- 使用智能指针管理资源
注意:智能指针释放资源的默认行为是delete,但是这里我们是想要解锁即可,不必将锁释放,这个时候就可以用到智能指针的自定义删除器Deleter
struct Lock {
private:
shared_ptr mutexPtr;
public:
// 将unlock函数设置为删除器
explicit Lock(MyMutex *mutex) : mutexPtr(mutex, unlock) {
lock(mutexPtr.get());
}
// 不必声明析构函数,因为mutexPtr是栈上对象,所以会被默认释放,那么智能指针就会调用其释放器unlock
};
复制底部资源(深拷贝)或者转移资源管理权(移动语义)
在资源管理类中提供对原始资源的访问
- API常需要要求访问原始资源,所以RAII资源管理类应该提供访问原始资源的接口
- 对原始资源可以由显示转换或者隐式转换获得.其在安全性和方便性上各有取舍.
在其中要注意,不可以get一个智能指针去初始化另一个智能指针,否则会发生重复释放
int main() {
shared_ptr p1 = make_shared();
{
shared_ptr p2(p1.get());
cout << p1.use_count() << " " << p2.use_count() << endl;
// 1 1
// p2离开代码块,释放其管理的资源,p1指针指向被释放的内存
}
}
程序将异常退出
显式/隐式资源转换struct Resource {
string str;
Resource(string s) : str(s) {}
};
struct Manager {
private:
Resource *resource;
public:
Manager(Resource *r) : resource(r) {}
~Manager() {
delete resource;
}
// 显式转换,获得其资源
Resource *getResource() {
return resource;
}
// 隐式转换,获得其资源
operator Resource *() {
return resource;
}
};
void useResource(Resource *resource) {
cout << resource->str << endl;
}
int main() {
Manager manager(new Resource("Hello"));
useResource(manager.getResource());
Manager manager1(new Resource("World"));
useResource(manager1);
}
一般推荐使用显式转换(get),避免潜在错误



