1.智能指针的原理
1.1RAII
RAII叫做资源获取即初始化,是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。 通常是将资源封装到一个类中,将资源管理的责任交给一个对象。
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效
1.2实现一个自己的智能指针
template<class T>
class smartprt
{
public:
smartprt(T* ptr)
:_ptr(ptr)
{}
smartptr(smartptr<T>&ptr)
:_ptr(ptr)
{}
~smartprt()
{
cout << "delete:" << ptr << endl;
delete _ptr; //释放内存
_ptr = nullptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
private:
T* _ptr;
};
测试
struct Date
{
int _year;
int _month;
int _day;
};
int main()
{
smartptr<int>ptr1(new int);
*ptr1 = 10;
cout << *ptr1 << endl;
smartptr<Date>date(new Date);
/*
这一步编译器进行了优化,原调用应该是
date.operator->()->_year; 编译器为了可读性
省略了operator->()的调用
*/
date->_year = 2022;
date->_month = 9;
date->_day = 22;
cout << "year:" << date->_year << endl;
cout << "_month:" << date->_month << endl;
cout << "_day:" << date->_day << endl;
return 0;
}
1.2.1拷贝出现的二次析构问题
上面smartptr类,会出现二次析构问题。(比如我们拷贝一个指针,那么指针指向的内存可能被释放多次)
void func()
{
smartptr<Date>date1(new Date);
date1->_year = 2022;
date1->_month = 9;
date1->_day = 22;
smartptr<Date>date3 = date1;
}
int main()
{
func();
return 0;
}
2.标准库中的智能指针
2.1std::auto_ptr
C++98版本的库中就提供了auto_ptr的智能指针。其核心思想是将内存的管理权移交。【结果是无论如何拷贝,都只有一个指针可以对内存进行管理】。
下面实现一个简化版本的auto_ptr
template<class T>
class myauto_ptr
{
typedef myauto_ptr<T> self;
public:
//构造函数
myauto_ptr(T*ptr):_ptr(ptr)
{}
~myauto_ptr()
{
if (_ptr)
{
cout << "delete" << _ptr << endl;
delete _ptr;
_ptr = nullptr;
}
}
//拷贝构造,移交管理权
myauto_ptr(myauto_ptr<T>&au)
:_ptr(au._ptr)
{
au._ptr = nullptr;
}
//赋值运算符重载
myauto_ptr<T>& operator=(myauto_ptr<T>&aut)
{
_ptr = aut._ptr;
aut._ptr = nullptr;
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
private:
T* _ptr;
};
测试
void fun2()
{
myauto_ptr<Date>date1(new Date);
date1->_year = 2022;
date1->_month = 9;
date1->_day = 22;
myauto_ptr<Date>date2(date1);
cout << "date2->_year" << date2->_year << endl;
cout << "date2->month" << date2->_month << endl;
cout << "date2->day" << date2->_day << endl;
cout << "date1->_year" << date1->_year << endl;
cout << "date1->month" << date1->_month << endl;
cout << "date1->day" << date1->_day << endl;
}
int main()
{
fun2();
return 0;
}
表明只有一个指针具有管理和访问内存的权限
2.2std::unique_ptr
unique_ptr的实现原理:简单粗暴的防拷贝。
template<class T>
class myunique_ptr
{
public:
//构造函数
myunique_ptr(T* ptr) :_ptr(ptr)
{}
~myunique_ptr()
{
if (_ptr)
{
cout << "delete" << _ptr << endl;
delete _ptr;
_ptr = nullptr;
}
}
//防止拷贝
//方法一:使用关键字delete
//方法二:自己在类中声明并实现什么都不干的拷贝构造和赋值运算符重载
myunique_ptr(const myunique_ptr<T>& q) = delete;
myunique_ptr<T>& operator=(const myunique<T>& sp) = delete;
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
private:
T* _ptr;
};
2.3std::shared_ptr
C++中第一个好用的智能指针。
**shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。 **
- shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减1
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了
template<class t>
class myshared_ptr
{
public:
void release()
{
if (--(*_count) == 0 && _ptr)
{
cout << "delete" << _ptr << endl;
delete _ptr;
_ptr = nullptr;
delete _count;
_count = nullptr;
}
}
myshared_ptr(t* ptr)
:_ptr(ptr)
,_count(new int(1))
{}
~myshared_ptr()
{
release();
}
t& operator*()
{
return *_ptr;
}
t* operator->()
{
return _ptr;
}
t* get()
{
return _ptr;
}
myshared_ptr(const myshared_ptr<t>& sp)
:_ptr(sp._ptr)
, _count(sp._count)
{
(*_count)++;
}
//赋值运算符重载
myshared_ptr<t>& operator=(const myshared_ptr<t>& sp)
{
//if (this != &sp)
if (_ptr != sp._ptr)
{
release();
_ptr=sp._ptr;
_count =sp._count;
++(*_count);
}
return *this;
}
private:
t* _ptr;
int* _count;
};
测试
void fun3()
{
myshared_ptr<Date>date1(new Date);
date1->_year = 2022;
date1->_month = 9;
date1->_day = 22;
myshared_ptr<Date>date2(date1);
myshared_ptr<Date>date3 = date1;
cout << "date2->_year" << date2->_year << endl;
cout << "date2->month" << date2->_month << endl;
cout << "date2->day" << date2->_day << endl;
cout << "date1->_year" << date1->_year << endl;
cout << "date1->month" << date1->_month << endl;
cout << "date1->day" << date1->_day << endl;
}
int main()
{
fun3();
return 0;
}
2.4shared_ptr的线程安全问题
- 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是
- 这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是线程安全的。
- 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。
解决方法:对_count的操作都加锁
template<class t>
class myshared_ptr
{
public:
void release()
{
_pmtx->lock();
if (--(*_count) == 0 && _ptr)
{
cout << "delete" << _ptr << endl;
delete _ptr;
_ptr = nullptr;
delete _count;
_count = nullptr;
}
_pmtx->unlock();
}
myshared_ptr(t* ptr)
:_ptr(ptr)
,_count(new int(1))
, _pmtx(new mutex)
{}
~myshared_ptr()
{
release();
}
t& operator*()
{
return *_ptr;
}
t* operator->()
{
return _ptr;
}
t* get()
{
return _ptr;
}
myshared_ptr(const myshared_ptr<t>& sp)
:_ptr(sp._ptr)
, _count(sp._count)
, _pmtx(sp._pmtx)
{
_pmtx->lock();
(*_count)++;
_pmtx->unlock();
}
//赋值运算符重载
myshared_ptr<t>& operator=(const myshared_ptr<t>& sp)
{
//if (this != &sp)
if (_ptr != sp._ptr)
{
release();
_ptr=sp._ptr;
_count =sp._count;
_pmtx->lock();
++(*_count);
_pmtx->unlock();
}
return *this;
}
private:
t* _ptr;
int* _count;
mutex* _pmtx;
};
2.5循环引用和weak_ptr
2.5.1循环引用
shared_ptr虽然功能交其他的智能指针功能更加强大,但是同样有其问题,其中一个就是循环引用的问题。如:
struct ListNode
{
std::shared_ptr<ListNode> prev = nullptr;
std::shared_ptr<ListNode> next = nullptr;
int _val = 0;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
std::shared_ptr<ListNode>ptr1(new ListNode);
std::shared_ptr<ListNode>ptr2(new ListNode);
ptr1->next = ptr2;
ptr2->prev = ptr1;
return 0;
}
按照预期,ptr1和ptr2析构时会分别释放对应的ListNode结构,但结果是并没有释放内存
shared_ptr的底层是实现的引用计数,上面的代码逻辑为:
当main函数结束,ptr1和ptr2发生析构时:
2.5.2weak_ptr
为了解决shared_ptr出现的循环引用问题,C++11引入了weak_ptr。
weak_ptr可以访问对应的内存,但是不参与资源的管理。
struct ListNode
{
std::weak_ptr<ListNode> prev ;
std::weak_ptr<ListNode> next ;
int _val = 0;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
std::shared_ptr<ListNode>ptr1(new ListNode);
std::shared_ptr<ListNode>ptr2(new ListNode);
ptr1->next = ptr2;
ptr2->prev = ptr1;
return 0;
}
2.5.3weak_ptr模拟实现
weak_ptr的底层实现很简单,可以访问对应的内存,但是不参与资源的管理。
template <class T>
class myweak_ptr
{
myweak_ptr()
:_ptr(nullptr)
{}
myweak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
myweak_ptr<T>& operator(const shared_ptr<T>&sp)
{
if(_ptr!=sp.get())
{
_ptr=sp.get();
}
return *this;
}
private:
T* _ptr;
}
3.定制删除器
智能指针默认释放资源的方式是delete。
由于无法删除数组类型的指针或者FILE*的类型,所以出现了定制删除器。
struct Date
{
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
int main()
{
std::unique_ptr<Date>date1(new Date);
std::unique_ptr<Date>date2(new Date[10]);
std::unique_ptr<Date>date3((Date*)malloc(sizeof(Date) * 5));
//std::unique_ptr<FILE>ptr1((FILE*)fopen("test.cpp", "r"));
return 0;
}
发生了内存泄漏
定制删除器的底层就是一个仿函数
struct Date
{
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
template <class T>
struct del
{
void operator()(T* ptr)
{
delete[] ptr;
}
};
template <class T>
struct freed
{
void operator()(T* ptr)
{
free(ptr);
}
};
int main()
{
std::unique_ptr<Date>date1(new Date);
std::unique_ptr<Date,del<Date>>date2(new Date[10]);
std::unique_ptr<Date,freed<Date>>date3((Date*)malloc(sizeof(Date) * 10));
//std::unique_ptr<FILE>ptr1((FILE*)fopen("test.cpp", "r"));
return 0;
}
delete的底层是先调用析构函数,在调用operator delete。而operator delete又封装了free()