从C++的RAII理解智能指针的思路(一)
Contact me:
Blog -> https://cugtyt.github.io/blog/index
Email -> cugtyt@qq.com
GitHub -> Cugtyt@GitHub
假设我们有如下代码:
class MyClass;
void fun() {
MyClass mc;
// do sth. with mc
}
在fun函数结束的时候mc还存在吗?当然,这是c++的基本知识,我们创建mc的时候调用构造函数,离开作用域就调用析构函数,所以mc已经不存在了。
void fun() {
int* iarr = new int[10];
// do sth. wirh iarr
}
在fun函数结束的时候iarr还在吗?当然,iarr不在了,可是它申请的空间还是没有释放,这造成了内存泄漏。
在C++中,这种在生命周期结束时释放资源的方法被称作资源获取即初始化(Resource Acquisition Is Initialization (RAII))。我们能不能把这个特性用到资源管理上呢?
class Unique {
public:
Unique(int n) {
std::cout << "Unique" << std::endl;
ptr = new int[n];
}
~Unique() {
std::cout << "~Unique" << std::endl;
delete []ptr;
}
// other func
private:
int *ptr;
};
int main() {
Unique u1(10);
// do sth. with u
}
Output:
Unique
~Unique
这样一个最为粗糙和简陋的管理方法就出现了,在函数结束的时候内存也释放了,当然我们拿int数组作为例子。很好,但是只能适用简单情况,很多问题不能处理,比如,我们不能随意把ptr暴露出来。如果外面获取了ptr,把内存释放掉了,那么等我们释放的时候就是第二次释放了,会出问题的。同样也不能实现内存共享,对象复制也受到影响等。
Unique u1{10};
Unique u2 = u1; // !!!
Output:
Unique
~Unique
~Unique
上面的代码会造成两个指针同时指向一片区域,调用构造函数就二次释放了,我们需要避免它。我们可以简单粗暴的把赋值构造函数和拷贝构造函数delete就可以解决意外复制的情况:
class Unique {
public:
...
Unique(const Unique&) = delete;
Unique& operator=(const Unique&) = delete;
...
}
这样,没有了拷贝,如果我们为了需要转移所有权,可以在函数里面写入对指针的判断:
class Unique {
public:
Unique(int n) {
std::cout << "Unique" << std::endl;
ptr = new int[n];
}
Unique(const Unique& u) {
this->ptr = u.ptr;
u.ptr = std::nullptr;
}
Unique& operator=(const Unique&) {/*与上面函数一样*/}
~Unique() {
std::cout << "~Unique" << std::endl;
if (ptr) {
delete []ptr;
}
}
// other func
private:
int *ptr;
};
int main() {
Unique u1(10);
Unique u2(u1);
}
虽然两次析构函数都会调用,但是内存正确释放一次,我们实现了unique_ptr的基本思路。注意这是模型,仅供理解,和c++内部实现并不一样。
那么shared_ptr,weak_ptr呢,后面我们继续讨论。