智能指针思想实践(std::unique_ptr, std::shared_ptr)
1 smart pointer 思想
个人认为smart pointer实际上就是一个对原始指针类型的一个封装类,并对外提供了-> 和 * 两种操作,使得其能够表现出原始指针的操作行为。
要理解smart pointer思想首先要了解一个概念RAII(Resource Acquisition Is Initialization), 直译为资源获取即初始化,核心理念为在对象创建时分配资源,而在对象销毁时释放资源.
根据RAII理念,如果对象创建在栈(stack)上,由于栈上的对象在销毁是会自动调用析构函数,因此仅仅需要在构造函数内完成资源分配,而在析构函数内完成资源释放,此时程序员就不需要自己关心资源的释放问题。
但当对象创建在自由存储区(free store)上时,例如:
class Fruit {
public:
Fruit(std::string name = "fruit", int num = 1) :name_{ name }, num_{ num }{}
~Fruit(){ cout << "destroy fruit" << endl;}
std::string name_;
int num_;
};
int main(){
Fruit* intPtr{new Fruit};//memory leak
return 0;
}
此时系统仅仅能回收在栈上1创建的指针intPtr所占据的资源,对于指针所指向的动态分配的内存空间并不会自动调用析构函数进行资源释放,此时如果程序员不主动调用 delete 进行资源释放则会产生内存泄漏。
那么如何让创建在自由存储区的对象也能够自动地释放资源,而不需要程序员自己手动释放资源呢?智能指针给出了一种非常巧妙的解决思路,它将一个原本定义在自由存储区的对象封装进了一个创建在栈上的资源管理对象中,由这个资源管理对象在自己的析构函数中释放定义在自由存储区上的对象所占据的资源。这使得程序员只需要利用资源管理对象接管在自由存储区上动态创建的对象资源,利用栈对象的生存机制能够实现资源的自动释放而不需要自己手动delete 对象资源。例如:
template <typename T>
class ResourceManager {
public:
ResourceManager(T* ptr) :ptr_{ ptr } {}
~ResourceManager() {
cout << "delete arr in free store" << endl;
delete ptr_;
}
private:
T* ptr_;
};
void AutoManage(){
ResourceManager fruit{ new Fruit};
}
int main(){
AutoManage();//delete arr in free store
system("pause");
//cout << fruit->name_ << " " << (*fruit).num_ << endl;//fruit 1
return 0;
}
在AutoManage()函数中动态分配一个Fruit对象,并将其封装进ResourceManager资源管理类中,当程序离开函数AutoManage()时,由于ResourceManager是一个定义在栈上的对象,程序会自动调用析构函数~ResourceManager()进行对象销毁操,此时由于ResourceManager在析构函数中进行了Fruit资源的释放,因此不会发生内存泄漏问题,一次不需要程序员手动释放资源的自动内存管理过程完美完成。
以上仅仅完成了动态分配的资源的自动回收功能,要使得ResourceManager资源管理类能够像Fruit*指针一样操作Fruit对象的成员,还需要对外提供***** 以及->两种指针操作:
template <typename T>
class ResourceManager {
public:
ResourceManager(T* ptr) :ptr_{ ptr } {}
~ResourceManager() {
cout << "delete arr in free store" << endl;
delete ptr_;
}
T*& operator->() {return ptr_;}
T& operator*() { return *ptr_; }
private:
T* ptr_;
};
void AutoManage(){
ResourceManager fruit{ new Fruit};
}
int main(){
AutoManage();//delete arr in free store
system("pause");
cout << fruit->name_ << " " << (*fruit).num_ << endl;//fruit 1
return 0;
}
此时可以利用ResourceManager提供的***** 以及->操作符直接操作原始Fruit* 指针,使得ResourceManager对象就像一个真实的指向Fruit对象的Fruit* 指针。
2 unique_ptr 思想
unique_ptr作为最常用的智能指针,它提供了对资源的独占式管理,即对资源的唯一所有权(sole ownership), 这就要求unique_ptr是一个不可复制的对象。每一个unique_ptr对象都有义务对其管理的资源进行释放。但unique_ptr 并不限制移动(move)操作所导致的所有权转移。最后不要忘记unique_ptr作为一个智能指针概念,它必须能够自动管理动态分配的对象资源,并且提供对对象资源的指针操作。概括一下,unique_ptr要求:
- 不可复制
- 能够移动
- 自动内存管理
- 指针操作
template<typename T>
class UniquePtr {
public:
UniquePtr(T* ptr):ptr_{ptr}{}
~UniquePtr() {
cout << "delete unique resource in free store" << endl;
delete ptr_;//释放资源
}
UniquePtr(const UniquePtr&) = delete;//禁用拷贝构造
UniquePtr& operator=(const UniquePtr&) = delete;//禁用拷贝复制
UniquePtr(UniquePtr&& object) {//移动构造
cout << "move construct" << endl;
ptr_ = object.ptr_;
object.ptr_ = nullptr;
}
UniquePtr& operator=(UniquePtr&& object) {//移动赋值
cout << "move assign" << endl;
ptr_ = object.ptr_;
object.ptr_ = nullptr;
return *this;
}
T*& operator->() { return ptr_; }//->
T& operator*() { return *ptr_; }//*
private:
T* ptr_;
};
template <typename T>
void ChangeOwnership(UniquePtr<T> move) {
UniquePtr<T> newOwner{ nullptr };
newOwner = std::move(move);
}
int main(){
UniquePtr uniquePtr{new Fruit};
ChangeOwnership(std::move(uniquePtr));
//ChangeOwnership(uniquePtr);//compile error! deny copy construction
//UniquePtr uniquePtr1 = uniquePtr;//compile error! deny copy construction
//UniquePtr<Fruit> uniquePtr2{nullptr};
//uniquePtr2 = uniquePtr;//compile error! deny copy assignment
system("pause");
return 0;
}