0x00 auto_ptr简介
auto_ptr 本身是 C++98 时代下提供的模板,在C++11及以上的环境下,不推荐使用。但是如果你的环境只支持C++98那么就只能使用它来智能的管理指针。
智能在使用时,跟一般指针一样。可以* ,-> 等等操作。
头文件:
#include <memory>
0x01 auto_ptr使用时的疑惑
首先写个简单的字符串类CStr 用来测试auto_ptr 。
class CStr
{
public:
CStr(const char* str = nullptr)
{
if (str != nullptr)
{
int nLen = strlen(str);
m_pStr = new char[nLen + 1];
strcpy_s(m_pStr, nLen + 1, str);
}
}
CStr(const CStr& obj)
{
int nLen = strlen(obj.m_pStr);
m_pStr = new char[nLen + 1];
strcpy_s(m_pStr, nLen + 1, obj.m_pStr);
}
CStr(CStr&& obj)
{
std::swap(m_pStr, obj.m_pStr);
}
~CStr()
{
if (m_pStr != nullptr)
{
delete m_pStr;
m_pStr = nullptr;
}
}
CStr& operator=(const CStr& obj)
{
if (m_pStr != nullptr)
{
delete m_pStr;
m_pStr = nullptr;
}
int nLen = strlen(obj.m_pStr);
m_pStr = new char[nLen + 1];
strcpy_s(m_pStr, nLen + 1, obj.m_pStr);
return *this;
}
CStr& operator=(CStr&& obj)
{
std::swap(m_pStr, obj.m_pStr);
return *this;
}
operator char* ()
{
return m_pStr == nullptr ? const_cast<char*>("NULL") : m_pStr;
}
public:
char* m_pStr;
};
然后使用auto_ptr :
1.0 疑惑1
下面的代码运行会报错:
int main(int argc, char* argv[])
{
CStr str1("string1");
auto_ptr<CStr> pStr1(&str1);
return 0;
}
1.1 疑惑2
我以为的auto_ptr 被放弃的理由时这样的:
std::auto_ptr<std::string> pError(new std::string("Error"));
std::auto_ptr<std::string> pErrorTwo = pError;
一开始我以为auto_ptr 没有写= 运算符,导致= 进行的是浅拷贝,然后两个智能指针出作用域析构的时候,会对同一个对象进行两次delete。
结果下面的代码会正常运行:
int main(int argc, char* argv[])
{
auto_ptr<CStr> pStr2(new CStr("string2"));
auto_ptr<CStr> pStr3 = pStr2;
cout << *pStr3.get() << endl;
return 0;
}
看了好多博客,发现想法都是和我差不多的,然后搞不懂原因我就读了一下auto_ptr 的源码实现。
0x02 auto_ptr源码
template <class _Ty>
class auto_ptr {
public:
using element_type = _Ty;
explicit auto_ptr(_Ty* _Ptr = nullptr) noexcept : _Myptr(_Ptr) {}
auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) {}
auto_ptr(auto_ptr_ref<_Ty> _Right) noexcept {
_Ty* _Ptr = _Right._Ref;
_Right._Ref = nullptr;
_Myptr = _Ptr;
}
template <class _Other>
operator auto_ptr<_Other>() noexcept {
return auto_ptr<_Other>(*this);
}
template <class _Other>
operator auto_ptr_ref<_Other>() noexcept {
_Other* _Cvtptr = _Myptr;
auto_ptr_ref<_Other> _Ans(_Cvtptr);
_Myptr = nullptr;
return _Ans;
}
template <class _Other>
auto_ptr& operator=(auto_ptr<_Other>& _Right) noexcept {
reset(_Right.release());
return *this;
}
template <class _Other>
auto_ptr(auto_ptr<_Other>& _Right) noexcept : _Myptr(_Right.release()) {}
auto_ptr& operator=(auto_ptr& _Right) noexcept {
reset(_Right.release());
return *this;
}
auto_ptr& operator=(auto_ptr_ref<_Ty> _Right) noexcept {
_Ty* _Ptr = _Right._Ref;
_Right._Ref = 0;
reset(_Ptr);
return *this;
}
~auto_ptr() noexcept {
delete _Myptr;
}
_NODISCARD _Ty& operator*() const noexcept {
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Myptr, "auto_ptr not dereferenceable");
#endif
return *get();
}
_NODISCARD _Ty* operator->() const noexcept {
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Myptr, "auto_ptr not dereferenceable");
#endif
return get();
}
_NODISCARD _Ty* get() const noexcept {
return _Myptr;
}
_Ty* release() noexcept {
_Ty* _Tmp = _Myptr;
_Myptr = nullptr;
return _Tmp;
}
void reset(_Ty* _Ptr = nullptr) noexcept {
if (_Ptr != _Myptr) {
delete _Myptr;
}
_Myptr = _Ptr;
}
private:
_Ty* _Myptr;
};
0x03 auto_ptr源码分析
3.0 私有数据
1.一个私有数据成员
仅有一个私有数据成员,类对象的指针:
private:
_Ty* _Myptr;
3.2 公有成员
2.公有的成员函数
3.2.0 get()
提供对私有数据成员的访问方法:
_NODISCARD _Ty* get() const noexcept {
return _Myptr;
}
3.2.1 重载* 和->
重载运算符* 和-> 分别用来取得对象的引用和对象的指针:
_NODISCARD _Ty& operator*() const noexcept {
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Myptr, "auto_ptr not dereferenceable");
#endif
return *get();
}
_NODISCARD _Ty* operator->() const noexcept {
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Myptr, "auto_ptr not dereferenceable");
#endif
return get();
}
3.2.2 release()
提供了释放类对象指针的方法:仅仅是将类对象指针置空,并返回原来的类对象指针。
_Ty* release() noexcept {
_Ty* _Tmp = _Myptr;
_Myptr = nullptr;
return _Tmp;
}
3.2.3 reset()
销毁指定对象并存储新的指针:
void reset(_Ty* _Ptr = nullptr) noexcept {
if (_Ptr != _Myptr) {
delete _Myptr;
}
_Myptr = _Ptr;
}
3.2.4 重载= 运算符
总体思路就是将= 右边的auto_ptr 对象中的指针置空,然后= 左边的auto_ptr 对象中保存= 右边auto_ptr 对象中的指针。如果= 左边的auto_ptr 对象中原来保存过指针,则会先释放这个指针。
假设= 左边的auto_ptr 对象为obj1 ,= 右边的auto_ptr 对象为obj2 。
obj2 调用release 方法:将obj2 保存的指针置空,并返回obj2 原来保存的指针。 obj1 调用reset 方法:如果obj2 原来保存的指针和obj1 保存的指针不同,则obj1 会先释放自己保存的指针,然后再保存obj2 返回的指针。如果obj2 原来保存的指针和obj1 保存的指针相同,则不做处理。
auto_ptr& operator=(auto_ptr& _Right) noexcept {
reset(_Right.release());
return *this;
}
auto_ptr& operator=(auto_ptr_ref<_Ty> _Right) noexcept {
_Ty* _Ptr = _Right._Ref;
_Right._Ref = 0;
reset(_Ptr);
return *this;
}
3.2.5 构造函数
explicit auto_ptr(_Ty* _Ptr = nullptr) noexcept : _Myptr(_Ptr) {}
auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) {}
auto_ptr(auto_ptr_ref<_Ty> _Right) noexcept {
_Ty* _Ptr = _Right._Ref;
_Right._Ref = nullptr;
_Myptr = _Ptr;
}
构造函数的参数可知,要么不初始化,要么传一个有效的合法值:
auto_ptr<CStr> pstr1;
auto_ptr<CStr> pStr2(new CStr("string2"));
3.2.6 析构函数
析构函数直接delete类对象指针:
~auto_ptr() noexcept {
delete _Myptr;
}
C++的new和delete是配合使用的,new在堆中申请内存空间,然后调用对象的构造函数,delete调用对象的析构函数,然后释放在堆中申请的内存空间。
析构函数中,直接delete 对象指针,也就说明这个对象指针_Myptr 指向堆空间,也就是说这个对象是new 申请的。
因此下面的代码一定会产生异常:看 4.0 解惑1
int main(int argc, char* argv[])
{
CStr str1("string1");
auto_ptr<CStr> pStr1(&str1);
return 0;
}
0x04 解惑
4.0 解惑1
int main(int argc, char* argv[])
{
CStr str1("string1");
auto_ptr<CStr> pStr1(&str1);
return 0;
}
首先看一下auto_ptr 的析构函数。
str1 对象是在栈空间中,初始化pStr1 时没有任何问题,pStr1 会保存str1 对象的指针。然后pStr1 析构,直接delete 保存的str1 对象的指针,delete 时调用str1 对象的析构函数,然后释放str1 对象的空间,实际上str1 对象这块内存在栈空间中,不在堆空间中,因此会发生异常。
4.1 解惑2
int main(int argc, char* argv[])
{
auto_ptr<CStr> pStr2(new CStr("string2"));
auto_ptr<CStr> pStr3 = pStr2;
cout << *pStr3.get() << endl;
return 0;
}
查看auto_ptr 的= 运算符重载的源码可知,pStr2 对象会先调用release 方法,将保存的CStr 对象的指针返回给pStr3 ,然后pStr2 对象中的指针置空。 所以最后pStr3 中保存对象的指针,而pStr2 中的指针为空。在析构时,pStr3 先析构,析构时没有任何问题。然后pStr2 析构时,由于保存的指针为空,本来应该有问题,但是再次查看auto_ptr 的析构函数,析构函数中使用关键字noexcept 不抛出异常。
0x05 auto_ptr总结
1.由auto_ptr 的析构函数可知,初始化auto_ptr 时,必须用堆空间中的指针值初始化。
2.由auto_ptr 的= 的源码可知,auto_ptr 对象可以赋值,没有问题
|