一、资源分配即初始化:定义一个类来进行资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放(构造函数负责把资源分配给你,析构函数负责释放资源)
建议:在任何时候都不要使用AutoPtr,除非不用调用拷贝构造、赋值运算符的重载
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)//外部用户没有提供,默认给成空
//构造函数负责分配资源
:_ptr(ptr)
{
cout << "AutoPtr:" << this<<endl;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
//析构函数:把资源拿走
~AutoPtr()
{
cout << "~AutoPtr:" << this << endl;
if (_ptr)
delete _ptr;
}
private:
T* _ptr;
};
struct A
{
int _a;
int _b;
int _c;
};
void TestAutoPtr()
{
int *p1 = new int;
*p1 = 10;
AutoPtr<int> ap1(new int);//此时的对象不能直接当作一个指针进行解引用
//使用*或->,需要重载*或->
*ap1 = 10;
A* p2 = new A;
p2->_b = 10;
AutoPtr<A> ap2(new A);
ap2->_b = 10;
delete p1;//在这种方式下,必须手动释放p1和p2
delete p2;
}
int main()
{
return 0;
}
这种智能指针的存在,使得函数返回的位置不需要释放空间,不需要关闭指针,调用析构函数处理。
这种结构存在的缺陷:浅拷贝的方式,拷贝构造后,两个对象会指向同一个空间,释放时会出现问题。【没有把拷贝构造函数实现出来,是一个浅拷贝,浅拷贝导致两个对象共用同一块资源,同一块资源被释放多次,出现问题】
void TestAutoPtr()
{
int *p1 = new int;//先申请一个整型的空间
int *p11(p1);//让p11也指向这个整型的空间
*p1 = 10;//把p1的内容改为10
AutoPtr<int> ap1(new int);//申请一端整型的空间,交给ap1进行管理
AutoPtr<int> ap11(ap1);//用ap1拷贝构造ap11
*ap1 = 10;//
//出了函数的作用域,p1和p2管理的单个的空间已经释放了,接着释放三个类对象
//销毁时先创建的后销毁:先销毁ap2,再销毁ap11,ap11通过ap1拷贝构造
//会出现问题:浅拷贝的方式
A* p2 = new A;
p2->_b = 10;
AutoPtr<A> ap2(new A);
ap2->_b = 10;
delete p1;//在这种方式下,必须手动释放p1和p2
delete p2;
}
此时不能采用深拷贝的方式解决,因为,深拷贝要求ap1拥有资源,要求ap11也拥有资源。这块空间是外部用户申请 的空间,不是当前类申请的空间,这个类只是负责管理空间,没有申请空间的权力,只有释放空间的权力。
2、解决方式:ap1拷贝构造ap11,将ap1的管理控制权交给ap11,将ap1中的资源转移到ap11(当前对象)
添加拷贝构造函数:
//拷贝构造函数
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)//先让ap和ptr共用同一块资源
{
//再让ap和ptr脱离关系
ap._ptr = NULL;
}
主函数中:
void TestAutoPtr()
{
int *p1 = new int;//先申请一个整型的空间
int *p11(p1);//让p11也指向这个整型的空间
*p1 = 10;//把p1的内容改为10
AutoPtr<int> ap1(new int);//申请一端整型的空间,交给ap1进行管理
AutoPtr<int> ap11(ap1);//用ap1拷贝构造ap11
//*ap1 = 10;此时操纵ap1程序会崩溃,因为ap1已经不存在了
//此时只能操纵ap11
*ap11=10;
A* p2 = new A;
p2->_b = 10;
AutoPtr<A> ap2(new A);
ap2->_b = 10;
delete p1;//在这种方式下,必须手动释放p1和p2
delete p2;
}
资源的转移:类的对象通过构造实现,若是要调用拷贝构造函数、运算符的重载,必须把参数转移到当前对象上去,但是仍然存在缺陷,ap1和原来的空间完全没有关系了(任何情况下都不要去使用AutoPtr),不熟悉AutoPtr实现原理的人很可能通过ap1去操作,就会出现问题:
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)//外部用户没有提供,默认给成空
//构造函数负责分配资源
:_ptr(ptr)
{
cout << "AutoPtr:" << this<<endl;
}
//拷贝构造函数
//将ap对象中的资源转移到*this(当前对象)
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)//先让ap和ptr共用同一块资源
{
//再让ap和ptr脱离关系
ap._ptr = NULL;
}
//赋值运算符的重载
AutoPtr<T>& operator=(AutoPtr<T>& ap)
{
if (this != &ap)
{
if (_ptr)
//先将当前对象里面管理的资源清理,再接收当前资源里面的资源
delete _ptr;
//把ap的资源转移
_ptr = ap->_ptr;
//和ap脱离关系
ap._ptr = NULL;
}
//是自己给自己赋值
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
//析构函数:把资源拿走
~AutoPtr()
{
cout << "~AutoPtr:" << this << endl;
if (_ptr)
delete _ptr;
}
private:
T* _ptr;
};
struct A
{
int _a;
int _b;
int _c;
};
void TestAutoPtr()
{
int *p1 = new int;
int *p11(p1);
*p1 = 10;
p11 = p1;
AutoPtr<int> ap1(new int);
AutoPtr<int> ap11(ap1);
*ap11 = 10;
AutoPtr<int> ap3(new int);
ap3 = ap11;//此时会调用赋值运算符的重载
//此时ap3和ap11共用同一块空间,也会发生浅拷贝
A* p2 = new A;
p2->_b = 10;
AutoPtr<A> ap2(new A);
ap2->_b = 10;
delete p1;//在这种方式下,必须手动释放p1和p2
delete p2;
}
(3)改进会直接和原来空间脱离关系的情况:不让其与原来的空间完全脱离关系。对c++98AutoPtr的改进/优化
原理:资源释放管理权的转移
ap前面加上了一个const:AutoPtr(const AutoPtr<T>& ap),再函数体里面要进行修改当前对象里面的成员变量是不成立的,以下代码调用拷贝构造函数会发生错误:
//对c++98AutoPtr的改进/优化
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)
:_ptr(ptr)
, _owner(NULL)
{
if (_ptr)//当前对象管理资源了
_owner = true;
}
//需要给出拷贝构造和赋值运算符的重载,否则是浅拷贝的方式
AutoPtr(const AutoPtr<T>& ap)
:_ptr(ap._ptr)
, _owner(ap._owner)
{
ap._owner = false;
}
~AutoPtr()
{
if (_owner&&_ptr)
delete _ptr;
}
private:
T* _ptr;
bool _owner;
};
//调用拷贝构造函数
void TestAutoPtr()
{
//通过构造函数创建ap1
AutoPtr<int> ap1(new int);
//通过ap1拷贝构造ap2
AutoPtr<int> ap2(ap1);
}

此时,我们偏想让这个代码通过编译,可进行以下操作:加上关键字mutable
private:
T* _ptr;
mutable bool _owner;
即使是const类型的变量也是可以修改的
此时实现了一个对象拷贝另一个对象,两个对象都可以操作空间,但是只有当前对象对空间有释放的权力
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)
:_ptr(ptr)
, _owner(NULL)
{
if (_ptr)//当前对象管理资源了
_owner = true;
}
//需要给出拷贝构造和赋值运算符的重载,否则是浅拷贝的方式
AutoPtr(const AutoPtr<T>& ap)
:_ptr(ap._ptr)
, _owner(ap._owner)
{
ap._owner = false;
}
//重载符号
T& operator*()
{
return *_ptr;
}
~AutoPtr()
{
if (_owner&&_ptr)
delete _ptr;
}
private:
T* _ptr;
mutable bool _owner;
};
//调用拷贝构造函数
void TestAutoPtr()
{
//通过构造函数创建ap1
AutoPtr<int> ap1(new int);
//通过ap1拷贝构造ap2
AutoPtr<int> ap2(ap1);
*ap1 = 20;
*ap2 = 55;
}
4、在实现拷贝构造的基础上实现运算符的重载
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)
:_ptr(ptr)
, _owner(NULL)
{
if (_ptr)//当前对象管理资源了
_owner = true;
}
//需要给出拷贝构造和赋值运算符的重载,否则是浅拷贝的方式
AutoPtr(const AutoPtr<T>& ap)
:_ptr(ap._ptr)
, _owner(ap._owner)
{
ap._owner = false;
}
//赋值运算符的重载
AutoPtr<T>& operator=(const AutoPtr<T>& ap)
{
if (this != &ap)
{
if (_owner&&_ptr)
delete _ptr;
//让ap接收当前对象里面的资源
_ptr = ap._ptr;
_owner = ap._owner;
//ap里面的资源没有释放的权力,将owner改为false
ap._owner = false;
}
return *this;
}
//重载符号
T& operator*()
{
return *_ptr;
}
~AutoPtr()
{
if (_owner&&_ptr)
delete _ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
mutable bool _owner;
};
//调用拷贝构造函数
void TestAutoPtr()
{
//通过构造函数创建ap1
AutoPtr<int> ap1(new int);
//通过ap1拷贝构造ap2
AutoPtr<int> ap2(ap1);
AutoPtr<int> ap3;
*ap1 = 20;
*ap2 = 55;
ap3 = ap2;
*ap3 = 90;
}
int main()
{
TestAutoPtr();
system("pause");
return 0;
}
这两个版本AutoPtr,第二个版本中存在一个更加严重的缺陷,下述代码会发生问题,使代码崩溃:
void TestAutoPtr()
{
//通过构造函数创建ap1
AutoPtr<int> ap1(new int);
//通过ap1拷贝构造ap2
AutoPtr<int> ap2(ap1);
*ap1 = 20;
*ap2 = 55;
if (true)
{
AutoPtr<int> ap3;
ap3 = ap2;
*ap3 = 90;
}
*ap2 = 88;
}
ap3出了作用域对代码进行销毁,但是ap1和ap2不知道,ap1、ap2成为了野指针,导致程序崩溃。
继续进行改进,将版本恢复到第一个版本
c++中boost库实现智能指针
二、为了解决autoPtr中的问题而引入的一部分智能指针
1、scoped_ptr:解决调用拷贝构造,或者用一个对象给另外一个对象赋值的问题,只能管理单个的空间(一个类的对象最多只能拥有一个资源)
(1)先将拷贝构造函数和赋值运算符的重载给成私有的,则不能调用
//让拷贝构造函数和赋值运算符的重载函数不能调用
template<class>
class ScopedPtr
{
public:
ScopedPtr(T* ptr = NULL)
:_ptr(ptr)
{}
~ScopedPtr()
{
if (_ptr)
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;//管理空间的地址
}
private:
//必须给出拷贝构造函数,不然会使用默认的拷贝构造
//将函数给成私有的,则不能调用此函数
ScopedPtr(const ScopedPtr<T>& sp)
{}
ScopedPtr<T>& operator=(const ScopedPtr<T>& sp)
{
return *this;
}
private:
T* _ptr;//管理函数的指针
};
void TestScopedPtr()
{
ScopedPtr<int> sp1(new int);
//ScopedPtr<int> sp2(sp1);//用sp1拷贝构造sp2会失败
//ScopedPtr<int> sp2;//此函数中什么都不给,会构成一个空的地址
//sp2 = sp1;赋值操作也会失败,因为赋值运算符的重载是私有的
}
但是如果使用友元函数,则是可以调用私有的函数的
class ScopedPtr
{
friend void TestScopedPtr();
public:
(2)给拷贝构造函数和赋值运算符的重载只给出声明,不给定义。将这两个函数给出来,在编译的期间可以执行,但是在链接的时候会找不到
template<class>
class ScopedPtr
{
public:
ScopedPtr(T* ptr = NULL)
:_ptr(ptr)
{}
~ScopedPtr()
{
if (_ptr)
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;//管理空间的地址
}
ScopedPtr(const ScopedPtr<T>& sp);
ScopedPtr<T>& operator=(const ScopedPtr<T>& sp);
private:
T* _ptr;//管理函数的指针
};
void TestScopedPtr()
{
ScopedPtr<int> sp1(new int);
}
权限是公有的,用户进行了定义,也是可以调用的
template<class>
class ScopedPtr
{
public:
ScopedPtr(T* ptr = NULL)
:_ptr(ptr)
{}
~ScopedPtr()
{
if (_ptr)
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;//管理空间的地址
}
ScopedPtr(const ScopedPtr<T>& sp);
ScopedPtr<T>& operator=(const ScopedPtr<T>& sp);
private:
T* _ptr;//管理函数的指针
};
template<class T>
ScopedPtr<T>& ScopedPtr<T>::operator = (const ScopedPtr<T>& sp)
{
return *this;
}
void TestScopedPtr()
{
ScopedPtr<int> sp1(new int);
}
将这两种方式结合起来即可:为了防止用户在外部调用,将只给出的函数的声明写在private作用域内
template<class>
class ScopedPtr
{
public:
ScopedPtr(T* ptr = NULL)
:_ptr(ptr)
{}
~ScopedPtr()
{
if (_ptr)
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;//管理空间的地址
}
private:
ScopedPtr(const ScopedPtr<T>& sp);
ScopedPtr<T>& operator=(const ScopedPtr<T>& sp);
private:
T* _ptr;//管理函数的指针
};
template<class T>
ScopedPtr<T>& ScopedPtr<T>::operator = (const ScopedPtr<T>& sp)
{
return *this;
}
void TestScopedPtr()
{
ScopedPtr<int> sp1(new int);
}

(3)在C++11中,可以不给成私有的,在声明后写上等于delete
ScopedPtr(const ScopedPtr<T>& sp)=delete;
ScopedPtr<T>& operator=(const ScopedPtr<T>& sp)=delete;
2、scope_array:管理连续的空间,防止被拷贝
template<class T>
class ScopedArray
{
public:
ScopedArray(T* ptr=NULL)
:_ptr(ptr)
{}
~ScopedArray()
{
if (_ptr)
delete[] _ptr;
}
//重载下标运算符
T& operator[](size_t index)
{
return _ptr[index];
}
const T& operator[](size_t index)const
{
return _ptr[index];
}
private:
ScopedArray(ScopedArray<T>&);
ScopedArray<T>& operator=(ScopedArray<T>&);
private:
T* _ptr;
};
标准库中引入了ScopedPtr,但是没有ScopedArray,因为库中有vector且类型更加齐全。所有智能指针的头文件都包含在"#include<memory>中。标准库中存在unique_ptr:先调用operator new将空间申请出来;成功后调用构造函数【实现方式也是只给声明不给定义】
#include<memory>
int main()
{
unique_ptr<int> up1(new int);
//unique_ptr<int> up2(up1);不能进行拷贝
//unique_ptr<int> up2;
//up2 = up1;//不能赋值
return 0;
}
3、shared_array:
4、shared_ptr:引用计数
本文探讨了智能指针的概念,重点分析了AutoPtr在浅拷贝问题上的缺陷及解决方案,包括添加拷贝构造函数、使用mutable关键字以及对拷贝构造和赋值运算符的重载。同时介绍了C++中为解决智能指针问题引入的scoped_ptr、scoped_array、unique_ptr和shared_ptr等智能指针类型,强调了它们在资源管理上的不同策略。

2781

被折叠的 条评论
为什么被折叠?



