由于面试的时候被问到过几次智能指针,所以闲下来打算从头理一遍智能指针的实现思路,自己实现一个shared_ptr,也能够更好地理解智能指针。
智能指针说白了就是用类把指针封装起来,这样就不必担心内存泄漏的问题了,因为类的对象在出作用域的时候会自动调用析构函数。
因此智能指针要具备指针的基本功能:
- 首先要有一个T类型的指针,用于封装实际的指针(初始化);
- 其次一定要有一个int型指针,用于引用计数,同步更新指向同一内存空间的智能指针有多少个;
- 要自己写构造函数、拷贝构造函数、拷贝赋值函数及析构函数;
- 重载 解引用运算符 * 和 指向运算符 -> ;
- 最后useCount函数用于获取该对象所封装指针的引用计数。
另外还编写了几个测试样例,用于验证是否实现了shared_ptr的基本功能,具体代码如下:
#include <iostream>
using namespace std;
template<typename T>
class SharedPtr {
private:
T* m_ele;
int* m_num;
public:
SharedPtr(): m_ele(nullptr){
cout << "Hello Constructor 1 !" << endl;
m_num = new int(0);
}
SharedPtr(T* x): m_ele(x){
cout << "Hello Constructor 2 !" << endl;
m_num = new int(1);
}
SharedPtr(const SharedPtr& x){
cout << "Hello Copy Constructor !" << endl;
m_ele = x.m_ele;
m_num = x.m_num;
(*m_num)++;
}
/*
关于拷贝赋值函数,这里返回值加引用和不加引用区别很大:
如果返回值是引用对象的话,拷贝赋值时只会调用该赋值函数;
如果返回值是值对象,除了要调用该赋值函数,还要在该赋值函数结束时调用copy构造函数
创建一个临时对象作为返回值,返回后赋给等号左边的对象,
这个临时对象完成其使命后,在该赋值语句结束时,会调用析构函数,释放该临时对象
因此不加引用会比加引用多调用一次拷贝赋值和析构函数,这点一定要明确!
*/
SharedPtr& operator=(const SharedPtr& x){
cout << "Copy Assignment !" << endl;
(*m_num)--;
if((*m_num) == 0){
delete m_ele;
delete m_num;
}
m_ele = x.m_ele;
m_num = x.m_num;
(*m_num)++;
return *this; //不是构造函数,要有返回值!
}
T& operator*(){
return *(this->m_ele);
}
T* operator->(){
return this->m_ele;
}
~SharedPtr(){
cout << "Hello Destructor ! I'm " << *m_ele << endl;
//调用析构函数,则其引用计数减1,若引用计数减为0,则将其封装的指针和计数指针都释放掉
(*m_num)--;
if((*m_num) == 0){
delete m_ele;
delete m_num;
}
}
int useCount(){
return *(this->m_num);
}
};
int main() {
int* x=new int(520);
SharedPtr<int> test(x);
cout << "test引用计数: " << test.useCount() << " 指向内存的值:" << *test << endl;
int* y=new int(250);
SharedPtr<int> test2(y);
cout << "test2引用计数: " << test2.useCount() << " 指向内存的值:" << *test2 << endl;
SharedPtr<int> test3(test2);
cout << "test2引用计数: " << test2.useCount() << " 指向内存的值:" << *test2 << endl;
cout << "test3引用计数: " << test3.useCount() << " 指向内存的值:" << *test3 << endl;
test3 = test;
cout << "test引用计数: " << test.useCount() << " 指向内存的值:" << *test << endl;
cout << "test2引用计数: " << test2.useCount() << " 指向内存的值:" << *test2 << endl;
cout << "test3引用计数: " << test3.useCount() << " 指向内存的值:" << *test3 << endl;
return 0;
}
最终的输出结果表明该实现是准确的,结果如下: