智能指针是C++11的新特新之一,最要作用是可以防止内存泄漏。
为什么有智能指针
在智能指针出现以前,程序员使用普通指针指向堆区的空间,在使用完成之后非常容易忘记释放,导致内存泄漏,或者将指针传给其他函数,在此函数和另一个函数中都释放,导致重复释放使程序奔溃。
智能指针的出现便是为了解决这个问题。其中,智能指针分为三种类型:共享性智能指针,独占型智能指针,弱引用智能指针。使用智能指针时需要引入头文件<memory>。
智能指针可以在使用完之后自动释放堆区空间,防止内存泄漏,实现这个功能的核心是引用计数,每当有一个智能指针指向一块堆区空间时,所有指向这块空间的智能指针引用计数+1,当有一个智能指针不指向它时,对应的引用计数-1,当引用计数降为0时,即没有智能指针指向这块堆区空间时,才会释放这块内存,防止内存泄漏的同时,也一定程度上防止了重复释放。
共享型智能指针——shared_ptr
共享智能指针的初始化
共享智能指针是指多个智能指针可以同时指向同一块有效的内存,共享指针shared_ptr是一个模板类,如果进行初始化有三种方式如下:
- 通过构造函数初始化
- std::make_shared辅助函数
- reset方法
共享智能指针对象初始化完毕之后就指向了要管理的那块堆区内存,如果想要查看当前有多少个智能指针同时管理者这块内存可以使用成员函数use_count()
构造函数初始化
如果用一块有效内存初始化这块空间,这个智能指针的引用计数会+1,如果用nullptr或者没有初始化智能指针,则它的引用计数为0。注意:不要用普通指针初始化多个智能指针。
#include<iostream>
#include<memory>
using namespace std;
int main() {
//share_ptr:共享智能指针,一块堆区内存可以被多个智能指针指向
//初始化
//构造函数初始化
shared_ptr<int> ptr1(new int(520));
cout << "ptr1的引用计数:" << ptr1.use_count() << endl;
shared_ptr<char> ptr2(new char[520]);
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
shared_ptr<int> ptr3;//默认构造
cout << "ptr3的引用计数:" << ptr3.use_count() << endl;
shared_ptr<int> ptr4(nullptr);
cout << "ptr4的引用计数:" << ptr4.use_count() << endl;
//不要用原始指针初始化不同的智能指针,会导致重复释放同一块堆区空间
int* p = new int(12);
shared_ptr<int>p1(p), p2(p);
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
//此时p1,p2都指向同一块空间,但是引用计数都为1,重复释放同一块内存,运行错误
}
输出:
ptr1的引用计数:1
ptr2的引用计数:1
ptr3的引用计数:0
ptr4的引用计数:0
1
1
//运行错误
用普通指针初始化多个智能指针,会导致这几个智能指针虽然指向同一块堆区空间但是他们的引用计数都为一,会导致重复释放的问题发生。
拷贝构造和移动构造初始化
#include<iostream>
#include<memory>
using namespace std;
int main() {
//构造函数
shared_ptr<int> ptr1(new int(520));
//拷贝构造
shared_ptr<int> ptr2 = ptr1;
shared_ptr<int> ptr3 = ptr1;
//移动构造
shared_ptr<int>ptr4(move(ptr1));
shared_ptr<int>ptr5 = move(ptr2);
cout << "ptr1的引用计数:" << ptr1.use_count() << endl;
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
cout << "ptr3的引用计数:" << ptr3.use_count() << endl;
cout << "ptr4的引用计数:" << ptr4.use_count() << endl;
cout << "ptr5的引用计数:" << ptr5.use_count() << endl;
}
输出:
ptr1的引用计数:0
ptr2的引用计数:0
ptr3的引用计数:3
ptr4的引用计数:3
ptr5的引用计数:3
std::make_shared 初始化
通过C++11提供的std::make_shared()就可以完成内存对象的创建并将其初始化给智能指针
如果使用拷贝的方式初始化共享智能指针,这两个对象会同时管理同一块内存,堆内存对应的引用计数也会增加。如果使用移动构造的方式初始化智能指针对象,只是转让了内存的所有权,管理内存的对象不会增加,因此内存引用计数不会增加。
#include<iostream>
#include<memory>
using namespace std;
int main() {
shared_ptr<int>ptr1 = make_shared<int>(520);
cout << "ptr1的引用计数:" << ptr1.use_count() << endl;
}
输出:
ptr1的引用计数:1
reset方法初始化
对于一个未初始化的共享指针,可以通过reset方法来初始化,当智能指针中有值的时候,调用reset会修改当前指针对象的指向,其他指向原地址的智能指针引用计数-1。
#include<iostream>
#include<memory>
using namespace std;
int main() {
shared_ptr<int>ptr1 = make_shared<int>(520);
shared_ptr<int>ptr2 = ptr1;
shared_ptr<int>ptr3 = ptr1;
shared_ptr<int>ptr4 = ptr1;
cout << "ptr1的引用计数:" << ptr1.use_count() << endl;
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
cout << "ptr3的引用计数:" << ptr3.use_count() << endl;
cout << "ptr4的引用计数:" << ptr4.use_count() << endl<<endl;
ptr4.reset();
cout << "ptr1的引用计数:" << ptr1.use_count() << endl;
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
cout << "ptr3的引用计数:" << ptr3.use_count() << endl;
cout << "ptr4的引用计数:" << ptr4.use_count() << endl<<endl;
shared_ptr<int>ptr5;
ptr5.reset(new int(20));
cout << "ptr5的引用计数:" << ptr5.use_count() << endl;
}
输出:
ptr1的引用计数:4
ptr2的引用计数:4
ptr3的引用计数:4
ptr4的引用计数:4
ptr1的引用计数:3
ptr2的引用计数:3
ptr3的引用计数:3
ptr4的引用计数:0
ptr5的引用计数:1
获取原始指针
可以通过.get()方法获取智能指针的原始指针。
int main() {
shared_ptr<int>p(new int);
int* p1 = p.get();
cout << p1 << endl << p << endl;
}
输出:
015CD258
015CD258
//每次输出与上次不同,但二者值相同,代表相同的地址
独占型智能指针——unique_ptr
std::unique_ptr是一个独占智能指针,它不允许其他智能指针和它指向同一块内存,也没有引用计数,可以通过构造函数初始化一个独占智能指针,它的拷贝构造函数和赋值运算符重载函数被删除。
unique_ptr不允许被复制,但是可以通过函数返回值给其他的unique_ptr,还可以通过std::move()转移给其他的独占指针,但实际上还是一个独占指针独占一个地址。
使用reset可以让独占指针解除对原始内存的管理,也可以用来初始化一个独占智能指针。
可以使用get获取独占指针的原始指针。
class A {
public:
A() {};//默认构造
A(const A& other)=delete;//删除拷贝构造函数
};
unique_ptr<int> fun() {
unique_ptr<int> a(new int(10));
return a;//返回局部变量
}
int main() {
A a;
//A b = a;//报错,拷贝构造函数已被删除
unique_ptr<int> ptr1(new int(10));
//unique_ptr<int>ptr2 = ptr1;
//独占型智能指针的拷贝构造函数和赋值运算符重载函数被 delete 删除
//独占型智能指针可以使用移动构造
unique_ptr<int>ptr2(move(ptr1));
unique_ptr<int> ptr3 = fun();//移动构造
cout << *ptr3 << endl;
unique_ptr<int>ptr4(new int(10));
ptr4.reset();
//get()函数获取原始指针
unique_ptr<int>ptr5(new int(520));
int* p = ptr5.get();
cout << p << endl << ptr5 << endl;
}
输出:
10
01529BB8
01529BB8
弱引用智能指针——weak_ptr
弱引用智能指针std::weak_ptr 可以看做是 shared_ptr的助手,它不管理shared_ptr 内部的指针。std::weak_ptr 没有重载*和->,
因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为旁观者监视shared_ptr管理的资源是否存在,解决shared_ptr 循环引用的问题。
#include<iostream>
#include<memory>
using namespace std;
class B;
class A{
public:
shared_ptr<B>ptr;
~A() {
cout << "A 析构" << endl;
}
};
class B{
public:
//shared_ptr<A>ptr;
weak_ptr<A>ptr;
~B() {
cout << "B 析构" << endl;
}
};
int main() {
shared_ptr<A> ptr1(new A);
shared_ptr<B> ptr2(new B);
ptr1->ptr = ptr2;
ptr2->ptr = ptr1;
return 0;
}
若两个类中都使用共享智能指针,程序结束后,B的ptr2先释放此时引用计数为1,B的对象没有释放,然后A的ptr1释放,引用计数同样降为1,也没有释放,这就是循环引用,将其中一个成员的智能指针设置为weak_ptr就可以解决这个问题。读者可以自行将两种情况运行一遍。