RAII,也称资源获取即初始化,要求资源的有效期与持有资源的对象的生命期严格绑定,不会出现内存泄漏等问题。
我们尝试将指针封装到RAII类中,实现自动析构。
#include <iostream>
using namespace std;
template<typename T>
class RAII {
public:
RAII() :data(nullptr) {}
explicit RAII(T* rhs) :data(rhs) {}
~RAII() {
delete data;
}
T* operator ->() const {
return data;
}
private:
T* data;
};
struct A {
int x, y, z;
~A() {
cout << "~A();" << endl;
}
};
int main() {
RAII<A> ptr(new A{ 123, 456, 789 });
cout << ptr->x << " " << ptr->y << " " << ptr->z << endl;
return 0;
}
此时我们面临一个问题,此时调用以下代码:
RAII<A> ptr(new A{ 123, 456, 789 });
RAII<A> ptr2 = ptr;
ptr和ptr2中的data同时指向了同一个地址,换句话说,同时有了A的所有权。
程序退出时,析构函数会释放两次内存。
我们可以通过move移交所有权,确保只有一个指针指向A,减少进程退出时的内存释放次数。
以下代码禁用了拷贝构造函数:
template<typename T>
class RAII {
public:
RAII(const RAII<T>&) = delete; //禁用拷贝构造
RAII(RAII<T>&& rhs) { //万能引用, 支持move
data = rhs.data;
rhs.data = nullptr;
}
RAII& operator = (const RAII&) = delete;
void operator = (RAII<T>&& rhs) {
data = rhs.data;
rhs.data = nullptr;
}
};
int main(){
RAII<A> ptr(new A{ 123, 456, 789 });
RAII<A> ptr2 = move(ptr);
}
源代码:
#include <iostream>
using namespace std;
template<typename T>
class RAII {
public:
RAII() :data(nullptr) {}
RAII(const RAII<T>&) = delete; //禁用拷贝构造
RAII(RAII<T>&& rhs) { //万能引用, 支持move
data = rhs.data;
rhs.data = nullptr;
}
explicit RAII(T* rhs) :data(rhs) {}
~RAII() {
delete data;
}
RAII& operator = (const RAII&) = delete;
void operator = (RAII<T>&& rhs) {
data = rhs.data;
rhs.data = nullptr;
}
T* operator ->() const {
return data;
}
private:
T* data;
};
struct A {
int x, y, z;
~A() {
cout << "~A();" << endl;
}
};
int main() {
RAII<A> ptr(new A{ 123, 456, 789 });
cout << ptr->x << " " << ptr->y << " " << ptr->z << endl;
RAII<A> ptr2 = move(ptr);
return 0;
}
执行结果:
123 456 789 ~A();
我们看到虚构函数~A()只执行了一次。
关于生命周期相关问题本文章不做讨论。
原文:https://zhuanlan.zhihu.com/p/600337719
作者:严格鸽