通常内存管理中存在以下问题:
例如,如果类中有指针成员,浅拷贝造成两个对象的指针成员指向同一个内存。当程序运行结束,一块内存被析构了两次。
void test()
{
int *dp = new int[10];
delete dp[];
//忘记添加dp = nullptr;
}
上面这种情况导致后续万一在该函数使用了dp指针,造成程序崩溃。这是因为虽然
delete
虽然释放了内存,但是dp并没有改变指向位置,导致dp变成野指针。
为了解决上述问题,C++11中引入了智能指针。C++11标准在废弃的auto_ptr
基础上,引入了三种智能指针:
shared_ptr
unique_ptr
weak_ptr
C++ 智能指针底层是采用引用计数的方式实现的。简单的理解,智能指针在申请堆内存空间的同时,会为其配备一个整形值(初始值为 1),每当有新对象使用此堆内存时,该整形值 +1;反之,每当使用此堆内存的对象被释放时,该整形值减 1。当堆空间对应的整形值为 0 时,即表明不再有对象使用它,该堆空间就会被释放掉。
三种指针的都采用类模板实现,需要在使用时包含memory
头文件并指定数据的类型
该指针叫做共享指针,和 unique_ptr
、weak_ptr
不同的是,多个 shared_ptr
智能指针可以共同使用同一块堆内存。并且,由于该类型智能指针在实现上采用的是引用计数机制,即便有一个 shared_ptr
指针放弃了堆内存的“使用权”(引用计数减 1),也不会影响其他指向同一堆内存的 shared_ptr
指针(只有引用计数为 0 时,堆内存才会被自动释放)。
常见的使用shared_ptr
指针的方式有:
std::shared_ptr<int> p1; //不传入任何实参
std::shared_ptr<int> p2(nullptr); //传入空指针 nullptr
std::shared_ptr<int> p3(new int(10));
//调用拷贝构造函数
std::shared_ptr<int> p4(p3);//或者 std::shared_ptr p4 = p3;
如上所示,p3 和 p4 都是 shared_ptr 类型的智能指针,因此可以用 p3 来初始化 p4,由于 p3 是左值,因此会调用拷贝构造函数。需要注意的是,如果 p3 为空智能指针,则 p4 也为空智能指针,其引用计数初始值为 0;反之,则表明 p4 和 p3 指向同一块堆内存,同时该堆空间的引用计数会加 1。
std::shared_ptr<int> p5(std::move(p4)); //或者 std::shared_ptr p5 = std::move(p4);
移动构造是为了解决调用函数时返回的局部对象二次拷贝赋值的情况。直接将临时对象的内存作为己用。
make_pair
模板函数std::shared_ptr<int> p3 = std::make_shared<int>(10);
注意:同一普通指针不能同时为多个 shared_ptr
对象赋值,否则会导致程序发生异常。例如:
int* ptr = new int;
std::shared_ptr<int> p1(ptr);
std::shared_ptr<int> p2(ptr);//错误
初始化智能指针时,还可以指定释放内存的规则
在某些场景中,自定义释放规则是很有必要的。比如,对于申请的动态数组来说,shared_ptr 指针默认的释放规则是不支持释放数组的,只能自定义对应的释放规则,才能正确地释放申请的堆内存。
对于申请的动态数组,释放规则可以使用 C++11 标准中提供的 default_delete 模板类,我们也可以自定义释放规则.
//指定 default_delete 作为释放规则
std::shared_ptr<int> p6(new int[10], std::default_delete<int[]>());
//自定义释放规则
void deleteInt(int*p) {
delete []p;
}
//初始化智能指针,并自定义释放规则
std::shared_ptr<int> p7(new int[10], deleteInt);
另外还可以用匿名函数(lambda表达式
)来指定规则:
std:: shared_ptr<int> p8(new int[10], [](int *p){
delete []p;
});
这里我们先介绍弱指针。为什么要引入弱指针呢?这是因为在使用shared_ptr
时,有时可能出现循环引用的情况。请看下面的示例:
// intelligentPointer.cpp
#include
#include
using namespace std;
//1.没有借助弱智真weak_ptr会形成环形引用,对象无法销毁
template<typename T>
struct Node
{
//构造函数
Node(const T& data):_data(data),_pre(nullptr),_pnext(nullptr){}
~Node()
{
cout << "~Node()" << endl;
}
//成员变量
T _data;
shared_ptr<Node<T>>_pre;
shared_ptr<Node<T>>_pnext;
};
void test()
{
shared_ptr<Node<int>> sp1(new Node<int>(10));
shared_ptr<Node<int>> sp2(new Node<int>(20));
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
//两个智能指针此时形成了环形引用
sp1->_pnext = sp2;
sp2->_pre = sp1;
cout << sp1.use_count() << endl;
cout << sp2.use_count() <<endl;
//按理说,函数调用结束后应该将两个对象应该被销毁,但是这里没有发生销毁,sp1
//和sp2的引用从1变为2
}
int main()
{
test();
system("pause");
return 0;
}
这里的输出结果是:
1
1
2
2
按理说调用完test()
函数后,两个内部的对象应该被销毁,同时调用析构函数。但是这里并没有发生上述情况。这是因为,sp1
指向了sp2
,而sp2
又指向了sp1
,双方都在等待对方先销毁,然后自己再销毁。但是这里发生循环引用,导致函数调用结束后不能正常销毁这两个对象。
为了解决上述问题,引入了弱指针,弱指针只具有观测作用,不会使引用计数+1,更不能使用指向的对象。
#include
#include
using namespace std;
//2.借助弱指针,解决环形引用的问题。弱指针仅仅相当于可以指向,但是并不能解引用或者使用指向的对象
template<typename T>
struct Node2{
//2.弱指针不必初始化
Node2(const T& data):_data(data){}
~Node2()
{
cout << "~Node2()" << endl;
}
T _data;
weak_ptr<Node2<T>> _pre;
weak_ptr<Node2<T>> _pnext;
};
void test2()
{
shared_ptr<Node2<int>> sp1(new Node2<int>(11));
shared_ptr<Node2<int>> sp2(new Node2<int>(22));
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
sp1->_pnext = sp2;
sp2->_pre = sp1;
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
}
int main()
{
test2();
system("pause");
return 0;
}
输出结果:
1
1
1
1
~Node2()
~Node2()
这里明显发现,析构函数被调用了,内存都得到了释放。这就是弱指针的作用。
其实这里的弱指针作用某些方面等同于普通指针
Node
和* _pre Node
:* _pnext
- 都不会增加引用计数
- 在函数调用结束后都会被释放掉
下面的例子采用普通指针,虽然析构函数中没有将_pre
和_pnext
置为空,但是在函数调用结束后,栈中的所有数据都不存在了。这就是为什么说在某种程度等价的原因。
#include
#include
using namespace std;
template<typename T>
struct Node3
{
/* data */
Node3(const T& data):_data(data),_pre(nullptr),_pnext(nullptr){}
~Node3()
{
cout << "~Node3" <<endl;
}
T _data;
Node3<T>*_pre;
Node3<T>*_pnext;
};
void test3()
{
Node3<int>* sp1(new Node3<int>(11));
Node3<int>* sp2(new Node3<int>(22));
sp1->_pnext = sp2;
sp2->_pre = sp1;
}
int main()
{
test3();
system("pause");
return 0;
}
weak_ptr
和shared_ptr
之间可以相互转化,shared_ptr
可以直接赋值给它,它可以通过调用lock
函数来获得shared_ptr
。
未完待续……