C++标准库提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr 和weak_ptr
- C++98 提供了 auto_ptr 模板的解决方案
- C++11 增加unique_ptr、shared_ptr 和weak_ptr
使用智能指针的目的是为了更安全,更容易的管理动态内存。
在c++中,动态内存的管理式通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。动态内存的使用很容易出现问题,因为确保在正确的时间释放内存是极其困难的。有时使用完对象后,忘记释放内存,造成内存泄漏的问题。
智能指针本质就是一个类模板,它可以创建任意的类型的指针对象,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间。
一、auto_ptr
auto_ptr是c++98版本库中提供的智能指针,该指针解决上诉的问题采取的措施是管理权转移的思想,也就是原对象拷贝给新对象的时候,原对象就会被设置为nullptr,此时就只有新对象指向一块资源空间。
一个auto_ptr p1指针指向一块new出来的空间,p1给另一个智能指针赋值,p2会接管p1的所有权,如果此时再去操作p1这个对象的时候,程序会崩溃。
如果auto_ptr调用拷贝构造函数或者赋值重载函数后,如果再去使用原来的对象的话,那么整个程序就会崩溃掉(因为原来的对象被设置为nullptr),这对程序是有很大的伤害的.所以很多公司会禁用auto_ptr智能指针。
二、unique_ptr
unique_ptr同auto_prt一样也是采用所有权模式,即同一时间只能有一个智能指针可以指向这个对象,但之所以unique_ptr智能指针更加安全,是因为它相比于auto_pr而言禁止了拷贝操作,unique_ptr采用了移动赋值std::move()函数来进行控制权的转移。
特性:
- 基于排他所有权模式:两个指针不能指向同一个资源
- 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
- 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
unique_ptr<string> p1(new string("I'm Li Ming!"));
unique_ptr<string> p2(new string("I'm age 22."));
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;
p1 = p2; // 禁止左值赋值
unique_ptr<string> p3(p2); // 禁止左值赋值构造
unique_ptr<string> p3(std::move(p1));
p1 = std::move(p2); // 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样
cout << "p1 = p2 赋值后:" << endl;
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;
修饰赋值后效果与auto_ptr赋值一样
三、shared_ptr
共享指针share_ptr是一种可以共享所有权的智能指针,它允许多个智能指针指向同一个对象,并使用引用计数的方式来管理指向对象的指针(成员函数use_count()可以获得引用计数),该对象和其相关资源会在“最后一个引用被销毁”时候释放。
shared_ptr是为了解决auto_ptr在对象所有权上的局限性(auto_ptr是独占的),使用引用计数的机制上提供了可以共享所有权的智能指针。
1、shared_ptr在内部会维护着一份引用计数,用来记录该份资源被几个对象共享。
2、当一个shared_ptr对象被销毁时(调用析构函数),析构函数内就会将该计数减1。
3、如果引用计数减为0后,则表示自己是最后一个使用该资源的shared_ptr对象,必须释放资源。
4、如果引用计数不是0,就说明自己还有其他对象在使用,则不能释放该资源,否则其他对象就成为野指针
shared_ptr的循环使用
A类中有B的智能指针;
B类中有A的智能指针;
当他们交叉互相持有对方的管理对象时程序结束了,但是并没有释放内存
函数结束后,栈回收,首先回收pc pc被回收了cccc的引用计数器由2变成1,因为spc还在指向cccc,接着pb被回收,bbbb的引用计数器也由2变1,因为spb还在指向bbbb,函数结束,正常bbbb和cccc应该引用计数器归0被自动回收 但是spc和spb互相指向对方,导致循环使用,内存泄漏。
在使用shared_ptr智能指针时,要注意避免对象交叉使用智能指针的情况! 否则会导致内存泄露!
#include <iostream>
using namespace std;
class Count{
public:
Count(){
count = 1;
}
void Increase(){
count++;
}
void Decrease(){
count--;
}
int GetCount(){
return count;
}
private:
int count = 0; //不能直接赋值为1,防止成员声明时SmartPtr<int> sp,引用计数会错误为1
};
template<typename T>
class SmartPtr{
public:
SmartPtr(): ptr_(nullptr),count_(nullptr){}
explicit SmartPtr(T*ptr) : ptr_(ptr), count_(new Count()) {}
~SmartPtr(){
if(count_!=nullptr){
count_->Decrease();
if(count_->GetCount() == 0){
delete ptr_;
delete count_;
ptr_ = nullptr;
count_ = nullptr;
}
}
}
SmartPtr(const SmartPtr<T>& other){
ptr_ = other.ptr_;
count_ = other.count_;
count_->Increase();
}
SmartPtr<T>& operator=(const SmartPtr<T>& other) {
if(ptr_ == other.ptr_) {
return *this;
}
ptr_ = other.ptr_;
count_ = other.count_;
count_->Increase();
return *this;
}
T* get() {
return ptr_;
}
T* operator->() {
return ptr_;
}
T& operator*() {
return *ptr_;
}
int GetCnt(){
if (count_ == nullptr) return 0;
return count_->GetCount();
}
private:
Count* count_;
T* ptr_;
};
class Test {
int a{23};
string b{"asdad"};
};
int main(){
SmartPtr<Test> sp0;
cout<<"sp0: "<<sp0.GetCnt() <<endl;
{
SmartPtr<Test> sp1(new Test());
cout<<"sp1: "<<sp1.GetCnt() <<endl;
SmartPtr<Test> sp2(sp1);
cout<<"sp2: "<<sp2.GetCnt() <<endl;
sp0 = sp2;
cout<<"sp0: "<<sp0.GetCnt() <<endl;
}
cout<<"sp0: "<<sp0.GetCnt() <<endl;
return 0;
}
因此在shared_ptr底层中在对引用计数进行访问之前,首先对其加锁,当访问完毕之后,在对其进行解锁。所以shared_ptr的引用计数是线程安全的。
多个线程访问sharedptr指向的资源时,引用计数时线程安全的 但是要对管理对象进行修改就需要上锁。
四、weak_ptr
weak_ptr类的对象它可以指向shared_ptr,并且不会改变shared_ptr的引用计数。一旦最后一个shared_ptr被销毁时,对象就会被释放。
它的提出主要是解决shared_ptr存在循环引用的问题
weak_ptr不能单独使用,应该通过shared_ptr构造而来
weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
【博客17】利用weak_ptr解决 “循环引用“_weakptr如何解引用循环-CSDN博客
weak_ptr wpGirl_1; // 定义空的弱指针
weak_ptr wpGirl_2(spGirl); // 使用共享指针构造
wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针
智能指针常用函数:
get() 获取智能指针托管的指针地址
release() 取消智能指针对动态内存的托管
reset() 重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉