本博客主要记录以下内容:
1.auto_ptr(包括使用讲解和仿写)
2.unique_ptr(包括使用讲解和仿写)
3.shared_ptr(包括使用讲解和仿写)
4.weak_ptr(包括使用讲解和仿写)
使用示例及讲解:
int main()
{
int* p = new int(10);
//auto_ptr a_ptr1 = p;//不允许让普通指针为实参,进行隐式构造
auto_ptr<int> a_ptr1(p);
//delete p;//error 这句代码没错,会执行,但是对象a_ptr1的生存期到了之后调用析构函数时,会崩溃
//原因是p和a_ptr1指向同一个堆空间,delete p释放了该空间,但是a_ptr1的析构函数会对该空间进行再次释放,就会崩溃
//如下a_ptr1.get()是获取对象a_ptr1的指针成员变量
cout << "a_ptr1拥有的堆空间的首地址:" << a_ptr1.get() << "\t " << "p指向的堆空间的首地址:" << p << endl;
//发现a_ptr1和p指向同一个堆空间,所以为了避免重复释放堆空间引起的崩溃问题,一般会执行以下操作:
//auto_ptra_ptr1(p); p = NULL;即让裸指针失去对堆空间的拥有权
auto_ptr<int>a_ptr2 = a_ptr1;//允许隐式构造,a_ptr1的资源被转移给a_ptr2,a_ptr1失去对资源的拥有权
//auto_ptra_ptr3(a_ptr1);//用这句代替上句代码当然也可以,这个是显示构造
cout << "a_ptr1拥有的资源的首地址:" << a_ptr1.get() << "\t " << "a_ptr2拥有的资源的首地址:" << a_ptr2.get() << endl;
//发现a_ptr2拥有的资源的首地址就是一开始的时候a_prt1拥有的资源的首地址
//执行这句操作后,发现对象a_ptr1不再对资源有拥有权,a_ptr1的指针成员变量指向NULL
//所以这句代码是将a_ptr1拥有的资源转让给a_ptr2,a_ptr1失去对资源的拥有权,指针成员变量指向NULL
//auto_ptra_ptr3(p);//这句代码语法没错,但是这样会造成a_ptr3也指向p指向的资源的首地址
//但是a_ptr2也指向这块资源,这样的话a_ptr2和a_ptr3就会指向同一个资源
//对象生存期到了之后a_ptr2和a_ptr3都会调用析构函数,都会对拥有的资源进行delete释放,会引发对一个堆空间多次释放的问题,会导致程序崩溃
//所以为了从源头避免这个问题,当用p构造一个auto_ptr指针对象之后,就应该立即执行p = NULL,让p失去对资源的拥有权
//成员函数的使用
int* pp = a_ptr2.get();//a_ptr2.get()是将a_ptr2的成员指针变量以临时指针变量的形式返回
cout << "pp指向的堆空间的首地址:" << pp << endl;//这个时候pp存放的堆空间的首地址和a_ptr2的拥有的资源的首地址一样
//a_ptr2.reset();//将a_ptr2拥有的资源释放,a_ptr2的成员指针变量指向NULL
int* q = a_ptr2.release();//返回资源的首地址,将a_ptr2的指针成员变量指向NULL(注意:只是a_ptr2失去对资源的拥有权,但是并没有释放资源)
cout << "a_ptr2的成员指针变量指向的空间:" << a_ptr2.get() << "\t" << "q指向的资源的首地址:" << q << endl;
cout << "q指向的资源的内容:" << *q << endl;//输出10
//没有对bool运算符的重载,后面讲述的指针有对bool运算符的重载
cout << "/" << endl;
int* x = new int(100);
auto_ptr<int>a_p1(x);
cout << "a_p1拥有的资源的首地址:" << a_p1.get() << endl;
int* y = new int(200);
auto_ptr<int>a_p2(y);
cout << "a_p2拥有的资源的首地址:" << a_p2.get() << endl;
a_p1 = a_p2;
cout << "a_p1拥有的资源的首地址:" << a_p1.get() << endl;
cout << "a_p2拥有的资源的首地址:" << a_p2.get() << endl;
cout << "a_p1原本的资源的内容:" << *x << endl;//会输出随机值,因为原本的资源已经被释放了
//会先对a_p1拥有的资源进行释放,然后将a_p2的资源转让给a_p1
//然后让a_p2的成员指针变量指向NULL,即让a_p2失去对资源的拥有权
//对智能指针进行仿写的时候,发现我一直以来的错误观点
//我一直以为对一个指向NULL的指针delete会崩溃,后来发现对一个指向NULL的指针delete,不会发生错误
}
auto_ptr总结:
关于构造和赋值:
(1)不允许以裸指针为实参对auto_ptr指针进行隐式构造,只允许裸指针对auto_ptr进行显示的构造。允许用auto_ptr类型的对象对auto_ptr类型的对象进行隐式构造和显示构造,但是构造之后,资源拥有权会转移给新构造出来的auto_ptr对象,以前的对象不再有资源的拥有权。
(2)不要用裸指针对多个智能指针进行构造,否则会引发堆空间被多次释放,程序崩溃。
为了避免因为同一个裸指针构造多个auto_ptr对象而引发堆空间重复释放程序崩溃的问题,在构造之后,将裸指针置空。
(3)用一个auto_ptr对象给另一个auto_ptr对象进行赋值操作的时候,后者如果本身拥有资源,那么会先将后者本身的资源进行释放,然后前者的资源给后者,再将前者的成员指针变量置空,让前者失去对资源的拥有权。
关于auto_ptr的成员函数:
(1)get()函数返回对象的成员指针变量(即资源的首地址)。
(2)reset()函数将对象的资源释放,将对象的成员指针变量置NULL。
(3)release()函数将对象拥有的资源的首地址返回,将成员指针变量指向NULL(即让对象失去资源的拥有权,返回资源的首地址,并没有释放资源)。
#ifndef MAUTO_PTR_H
#define MAUTO_PTR_H
template<typename T>
class Mauto_ptr
{
public:
explicit Mauto_ptr(const T*& ptr = NULL)//explicit关键字是告诉编译器不允许用裸指针隐式构造
{
_ptr = ptr;//新对象获取裸指针的资源(注意没有将裸指针置NULL,所以原指针和新对象共享资源)
}
Mauto_ptr(Mauto_ptr& src)//拷贝构造函数
{
_ptr = src._ptr; //新对象获取资源的拥有权
src._ptr = NULL;//传进来的实参失去对资源的拥有权
}
~Mauto_ptr()
{
delete _ptr;//如果_ptr == NULL,这句代码也不会出错
}
T* get()//返回成员指针变量(该成员指针变量保存资源的首地址)
{
return _ptr;
}
T* release()//失去资源的拥有权,返回资源的首地址(注意该操作并没有释放资源)
{
T* tmp = _ptr;
_ptr = NULL;//失去对资源的拥有权
return tmp; //返回资源的首地址
}
void reset()//将拥有的资源释放,成员指针变量置空
{
delete _ptr; //释放拥有的资源
_ptr = NULL;//成员指针变量置空
}
//=、*、-> 运算符重载
Mauto_ptr& operator=(Mauto_ptr& src)//赋值运算符重载
{
if (this == &src)//如果自己给自己赋值,那么什么都不做,返回本身
{
return *this;
}
_ptr = src._ptr;//新对象获取资源的拥有权
src._ptr = NULL;//传进来的实参失去对资源的拥有权
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;//成员指针变量,保存资源的首地址,即指向对象拥有的资源
};
#endif
使用示例及讲解:
unique_ptr<int> fun()//如果有形参,参数必须是引用(指针),否则形参涉及拷贝构造,unique_ptr不允许
{
return unique_ptr<int>(new int(100));//OK,临时对象
}
int main()
{
int* p = new int(10);
//unique_ptr u_ptr1 = p;//不允许隐式构造
unique_ptr<int> u_ptr1(p);
cout << "u_ptr1拥有的堆空间的首地址:" << u_ptr1.get() << "\t" << "p指向的堆空间的首地址:" << p << endl;
//发现u_ptr1和p指向同一个堆空间,所以为了避免重复释放堆空间引起的崩溃问题,一般会执行以下操作:
//unique_ptru_ptr1(p); p = NULL;即让裸指针失去对堆空间的拥有权
//delete p;//error 这句代码没错,会执行,但是对象u_ptr1的生存期到了之后调用析构函数时,会崩溃,因为有对一个堆空间重复释放的问题
//不允许用一个裸指针对多个unique_ptr进行赋值或者拷贝构造等和资源有关的操作
//因为每一个unique_ptr对象生存期到了之后,都会对拥有的资源进行释放,会发生对同一个堆空间重复释放的问题,程序会崩溃
//所以任何造成“让多个unique_ptr指向同一个资源的”结果的操作,都要避免,否则就会对一个堆空间重复释放,程序会崩溃
//unique_ptru_ptr2 = u_ptr1;//error,不允许隐式构造
//unique_ptru_ptr2(u_ptr1);//error,不允许普通的拷贝构造
//unique_ptru_ptr2 = fun();//OK,允许用右值进行隐式拷贝构造
/*//OK,允许用右值对unique_ptr进行赋值操作
unique_ptru_ptr2;//OK
u_ptr2 = fun();//OK
*/
unique_ptr<int>u_ptr2(fun());//OK,允许用右值(在这里是即将销毁的临时对象)为实参拷贝构造
cout << "u_ptr2拥有的堆空间的首地址:" << u_ptr2.get() << "\t" << "u_ptr2的资源的内容为:" << *u_ptr2 << endl;
//u_ptr2 = u_ptr1;//error,不允许unique_ptr类型的对象之间的赋值操作
//成员方法:
//get()会返回对象的成员指针变量(以临时存在的方式返回)
cout << "u_ptr1拥有的资源的首地址:" << u_ptr1.get() << endl;
//reset()会将对象拥有的资源释放,成员指针变量置空
//release()会将对象的成员指针变量置空,返回资源的首地址(并没有释放资源,只是失去对资源的拥有权)
//int*q = u_ptr1.release();//OK,注意,在这里是以成员指针变量的类型返回,这里是int*
unique_ptr<int>u_ptr3(u_ptr1.release());
cout << "u_ptr1拥有的堆空间的首地址:" << u_ptr1.get() << "\t" << "u_ptr3拥有的堆空间的首地址:" << u_ptr3.get() << endl;
//operator bool;对bool运算符进行了重载,可以根据以下语句判断u_ptr1是否拥有资源
if (u_ptr1) {
cout << "u_ptr1有资源" << endl; }
else {
cout << "u_ptr1没有资源" << endl; }
cout << "swap()//" << endl;
//swap()//将对象拥有的资源交换
cout << u_ptr2.get() << " " << u_ptr3.get() << endl;
u_ptr2.swap(u_ptr3);
cout << u_ptr2.get() << " " << u_ptr3.get() << endl;
//=运算符重载
u_ptr3 = fun();//右值赋值给unique_ptr类型的指针,但是u_ptr3原来的资源并没有被释放
cout << "u_ptr3拥有的资源的首地址:" << u_ptr3.get() << "\t" << "u_ptr3的资源的内容为:" << *u_ptr3 << endl;
//注意u_ptr2也是用fun()构造的,但是u_ptr2和u_ptr3的资源不一样,请参考博客右值引用的实质。
cout << "访问u_ptr3原本的资源内容:" << *p << endl;//注意u_ptr3被fun()赋值之前拥有的资源和p指向的堆空间是同一个
//上面这句代码输出成功,说明fun()赋值给u_ptr3,u_ptr3并没有把原来拥有的资源释放
return 0;
}
unique_ptr总结:
关于构造和赋值:
(1)对于两个unique_ptr的对象(非右值)来说,unique_ptr杜绝了两个unique_ptr对象之间的操作(包括赋值和拷贝构造)。
(2)对于右值和unique_ptr的对象来说,允许右值和unique_ptr对象之间的操作,包括用右值拷贝构造(隐式也可以),用右值赋值。但是用右值给unique_ptr对象赋值时,如果unique_ptr原本有资源,那么原本的资源不会被释放。
(3)对于裸指针和unique_ptr的对象来说,用裸指针构造unique_ptr对象之后,裸指针仍旧指向资源,所以要注意不要用同一个裸指针构造多个unique_ptr对象,否则每个unique_ptr对象生存期到了之后,都会调用析构对象释放堆空间,会造成一个堆空间被重复释放,导致程序崩溃。所以为了误操作,用裸指针构造了unique_ptr对象之后,最好将裸指针置空。不允许用裸指针隐式构造unique_ptr对象,必须用显示构造。不允许裸指针对unique_ptr进行赋值操作。
关于unique_ptr的成员函数:
(1)get()函数返回对象的成员指针变量(即资源的首地址)
(2)reset()函数将对象的资源释放,将对象的成员指针变量置NULL
(3)release()函数将对象拥有的资源的首地址返回,将成员指针变量指向NULL(即让对象失去资源的拥有权,返回资源的首地址,并没有释放资源)
(4)operator boo;unique_ptr对bool运算符进行了重载,成员指针指向NULL则返回假,代表没资源;成员指针指向资源则为真,代表有资源
(5)unique_ptr释放资源用的是deleter,deleter会自动识别这个资源是应该用delete释放好还是用delete[]释放好。
#ifndef MUNIQUE_PTR_H
#define MUNIQUE_PTR_H
template<typename T>
class Munique_ptr
{
public:
explicit Munique_ptr(T* ptr = NULL)//允许裸指针显示构造
{
_ptr = ptr;
}
//右值引用--只能用来引用即将死亡的对象
Munique_ptr(const Munique_ptr&& src)//允许用右值属性的对象拷贝构造
{
_ptr = src._ptr;
src._ptr = NULL;
}
Munique_ptr& operator=(Munique_ptr&& src)//允许右值对象进行赋值操作,注意并没有将原资源释放
{
if (this == &src)
{
return *this;
}
_ptr = src._ptr;
src._ptr = NULL;
return *this;
}
~Munique_ptr()
{
delete _ptr;
}
T* get()
{
return _ptr;
}
T* release()//注意只是失去对资源的拥有权,并没有释放资源
{
T* tmp = _ptr;
_ptr = NULL;
return tmp;
}
void reset()
{
delete _ptr;
_ptr = NULL;
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
operator bool()
{
return _ptr != NULL;
}
private:
T* _ptr;
};
#endif
shared_ptr是共享指针,如果一个共享指针拥有的资源可以同时被其他共享指针拥有,指向同一个资源的共享指针共用同一个引用计数,引用计数为几就意味着这个资源被几个共享指针拥有,当有共享指针失去对资源的拥有权时,或者有共享指针生存期到了时,那么这个资源的引用计数就减1,当引用计数为1,就说明这个资源现在只被一个共享指针拥有。当某个共享指针的生存期到了时,同时资源的引用计数为1时(即这个资源只被这一个共享指针拥有时),那么析构函数里才会执行对资源的释放操作。
(注:所有的这些都注意,前提是不能用同一个裸指针同时构造两个共享指针对象,否则会出错,代码中给出示例)
使用示例及讲解:
int main()
{
//use.cout()函数可以获取现在的资源被几个shared_ptr对象拥有
int* p = new int(10);
//shared_ptrs_ptr1 = p;//error 不允许隐式构造
shared_ptr<int>s_ptr1(p);//OK,显示构造
cout << "s_ptr1的资源的首地址:" << s_ptr1.get() << "\t" << "p指向的空间首地址:" << p << endl;
cout << "s_ptr1拥有的资源的引用计数为:" << s_ptr1.use_count() << endl;
shared_ptr<int>s_ptr2;
//s_ptr2 = p;//error不允许用裸指针赋值
s_ptr2 = s_ptr1;
cout<< "s_ptr1的资源的首地址:" << s_ptr1.get() << "\t" << "s_ptr2的资源的首地址:" << s_ptr2.get() << endl;
cout << "s_ptr1拥有的资源的引用计数为:" << s_ptr1.use_count() << endl;
shared_ptr<int>s_ptr3(s_ptr2);//允许拷贝构造
cout << "s_ptr3的资源的首地址:" << s_ptr3.get() << endl;
cout << "s_ptr1拥有的资源的引用计数为:" << s_ptr1.use_count() << endl;
s_ptr3.reset();//s_ptr3失去对资源的拥有权,由于引用计数大于1,s_ptr3不对资源进行释放
cout << "s_ptr3成员指针指向为:" << s_ptr3.get() << endl;
cout << "s_ptr1拥有的资源的引用计数为:" << s_ptr1.use_count() << endl;
int* q = new int(100);
shared_ptr<int>s_ptr4(q);
cout << "s_ptr1的资源的首地址:" << s_ptr1.get() << "\t" << "和s_ptr4拥有同一个资源的对象的个数:" << s_ptr4.use_count() << endl;
s_ptr4.swap(s_ptr1);
cout << "s_ptr1的资源的首地址:" << s_ptr1.get() << "\t" << "s_ptr4的资源的首地址:" << s_ptr4.get() << endl;
cout << "s_ptr1拥有的资源的引用计数为:" << s_ptr1.use_count() << "\t" << "s_ptr4拥有的资源的引用计数为:" << s_ptr4.use_count() << endl;
/*
shared_ptrs_ptr5(q);
cout << "s_ptr5拥有的资源的首地址:" << s_ptr5.get() << "s_ptr5拥有的资源的引用计数:" << s_ptr5.use_count() << endl;
*/
//上面被注释掉的代码会是程序崩溃,原因是我们用q指针构造了两个shared_ptr对象,但是这两个对象拥有的资源的引用计数为1
//s_ptr1(和s_ptr4交换资源后,s_ptr4拥有的是q指向的资源 )和s_ptr4两个指针都指向p
//但是它们的资源的引用计数都是1
//所以它们俩生存期到了之后,会对自己的资源进行释放,同一个资源被释放两次,程序崩溃
return 0;
}
shared_ptr总结:
关于构造和赋值:
(1)不允许裸指针隐式构造shared_ptr对象;允许裸指针显式构造shared_ptr对象。不会修改裸指针的指向。不允许用一个裸指针构造多个shared_ptr对象,因为会造成同一个资源被多个对象拥有,资源的引用计数却少于这些对象的个数,这样会造成对一个堆空间进行重复释放,程序会崩溃。(我们说的一个资源的引用计数为几就代表被几个共享指针拥有,是排除同一个裸指针构造多个共享指针情况后的)。
(2)允许shared_ptr对象之间的赋值。比如s_ptr1 = s_ptr2;
如果s_ptr1的资源被多个共享指针拥有,那么执行完这句代码后,s_ptr1原来的资源的引用计数减1(因为s_ptr1将不再拥有这个资源),s_ptr2拥有的资源的引用计数加1(因为s_ptr1将也会拥有这个资源)。如果s_ptr1的原资源的引用计数为1,即如果只有s_ptr1一个共享指针拥有,那么原资源将会被释放,然后s_ptr2的资源的引用计数加1。(注意,两个共享指针指向同一个资源的话,那么资源的引用计数是同步对这两个指针而言的)。
关于shared_ptr的成员函数:
(1)reset()使对象失去对资源的拥有权,如果资源的引用计数为1(即只被这一个对象拥有),那么会先释放资源,再将成员指针变量置空。如果资源的引用计数大于1(即被多个对象拥有),那么会先将资源的引用计数减一,再将自己的成员指针变量置空。
(2)注意shared_ptr不含有release()函数
(3)swap()函数会将两个对象的资源交换
(4)unique()函数判断对象拥有的资源引用计数是否为1(即是否只被这一个对象拥有)
#ifndef MSHARED_PTR_H
#define MSHARED_PTR_H
#include
using namespace std;
//注意,用裸指针构造对象时,我们并没有将资源引用计数记录到map中,
//当我们用这个对象去构造其他对象了,我们才看map有没有记录,有的话,资源引用计数加一,
//没有的话,这才将资源地址添加到map里记录。
//也就是说,拷贝构造时,如果发现资源没有被添加到map里,那么资源肯定只被实参这一个对象拥有,
//且没有用这个实参拷贝构造果其他对象。
template<typename T>
class Mshared_ptr
{
public:
explicit Mshared_ptr(T* ptr = NULL)//explicit关键字限制编译器不允许隐式构造
{
_ptr = ptr;
}
Mshared_ptr(const Mshared_ptr& src)//拷贝构造
{
if (_count.end() == _count.find(src._ptr))//说明map里没有记录这个资源
{
_count.insert(make_pair(src._ptr, 1));//先将资源添加进去,以引用计数为1添加
}
_ptr = src._ptr;
_count[_ptr]++;
}
Mshared_ptr& operator=(const Mshared_ptr& src)
{
if (this == &src)//自己对自己赋值,返回原对象
{
return *this;
}
if (unique())//如果被赋值的对象现在拥有的资源只被它一个拥有,释放资源
{
delete _ptr;
}
else//说明资源被多个对象拥有,引用计数--就可以了,不用释放资源
{
_count[_ptr]--;
}
if (_count.end() == _count.find(src._ptr))//说明map里没有将src拥有的资源记录在里面
{
_count.insert(make_pair(src._ptr, 1));//用map记录资源引用计数
}
_ptr = src._ptr;
_count[_ptr]++;
return *this;
}
~Mshared_ptr()
{
if (unique())//如果资源引用计数为1,释放资源
{
delete _ptr;
}
else//如果资源被多个共享指针拥有,那么将引用计数减一
{
_count[_ptr]--;
}
_ptr = NULL;
}
T* get()
{
return _ptr;
}
void reset()
{
if (unique())//如果资源只被一个共享指针拥有,释放资源
{
delete _ptr;
}
else//如果资源被多个共享指针拥有,引用计数减1
{
_count[_ptr]--;
}
_ptr = NULL;
}
bool unique()
{
if ((_count.end() == _count.find(_ptr)) || (_count[_ptr] == 1))//如果map里没有记录这个资源(同样代表资源只被一个对象拥有),或者引用计数为1
{
return true;
}
return false;
}
int use_count()
{
if (_count.end() == _count.find(_ptr))//如果map里没有记录这个资源,说明资源只被一个对象拥有,返回1
{
return 1;
}
return _count[_ptr];//否则的话,返回资源的引用计数
}
T& operator[](int pos)
{
return _ptr[pos];
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
operator bool()
{
return _ptr != NULL;
}
private:
T* _ptr;
static map<T*, int> _count;//存放引用计数,注意map不允许键值重复,所以可以用来记录一个资源的引用计数
};
#endif
看weak_ptr之前先来看一下shared_ptr存在的问题:
代码:
class B;
class A
{
public:
A() {
cout << "A构造函数" << endl; }
~A() {
cout << "A析构函数" << endl; }
void fun(shared_ptr<B>& p) {
a_p = p; }
private:
shared_ptr<B> a_p;
};
class B
{
public:
B() {
cout << "B构造函数" << endl; }
~B() {
cout << "B析构函数" << endl; }
void fun(shared_ptr<A>& p) {
b_p = p; }
private:
shared_ptr<A> b_p;
};
int main()
{
A* a = new A();
B* b = new B();
shared_ptr<A>s_p1(a);
shared_ptr<B>s_p2(b);
cout <<"s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;
a->fun(s_p2);
b->fun(s_p1);
cout << "s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;
return 0;
}
对象a和对象b为被共享指针拥有的资源,可以看见对象a被s_p1和对象b内的shared_ptr < A >拥有,所以对象a(资源a)的引用计数为2。同理,对象b被s_p2和对象a内的shared_ptr < B >拥有,所以对象b(资源b)的引用计数为2。当shared_ptr< A >s_p1和shared_ptr< B >s_p1的生存期到了之后,由于资源的引用计数都是2,所以资源a和资源b并不会被释放。这个原因就是因为它们内部的共享指针都指向对方。形成了一个环,使对方作为资源的时候,引用计数都加1了。
当然,可以手动释放对象a或者对象b,但是前面三个指针都谈过,用指向堆空间的指针构造智能指针时,不要再对这个指针进行delete操作了,因为智能指针的诞生就是为了解决对资源自行管理,如果我们再自行delete堆空间,非常容易造成对一个堆空间多次释放,程序崩溃。对于上面的代码是例外,因为sared_ptr解决不了这个环的问题,我们只是测试一下手动delete,平常将资源赋给智能指针之后,一定不要自己手动delete资源。我们在这里尝试自己手动delete掉没有被释放的资源,看一下结果。(后面的weak_ptr的使用才是正常解决方案)
代码1(在主函数末尾添加delete a;):
运行结果:
注意:程序执行错误,只不过没有崩溃而已。退出码为随机值,这个是错误码,意味着我们操作不对。
A析构函数被调用了两次,也就是a对象被析构两次(这也是为什么返回的是错误码的原因),delete a,会调用a的析构函数,a内部的shared_ptr< B >也会调用析构函数,它指向b,引用计数为1,所以shared_ptr< B >会对b进行delete,所以对象b会被释放,b内部的shared_ptr< A >会调用析构函数,指向对象a,资源a已经自己调用析构函数了,但是资源引用计数在共享指针内部,值为1,所以shared_ptr< A >的内部会进行delete a。所以a调用了两次析构函数。
代码2(在主函数末尾添加delete b;):
运行结果:
对于代码2,分析和代码一样的。
代码3(在主函数末尾添加delete a; delete b):
运行结果:
对于代码3,执行delete a时候会造成b被析构两次了,delete b的时候,b已经被析构过了,所以空间里也没有东西,也不会调用析构函数了。
总之,不管同一个对象被析构几次,当对同一个对象进行多次析构的时候,不管是几次析构,只要多于一次,不管程序有没有崩溃,这都是赤裸裸的错误。
这个时候就产生了weak_ptr,这个是专门为了shared_ptr服务的。弱指针只允许用shared_ptr类型的对象拷贝构造(因为weak_ptr的诞生就是为了shared_ptr服务的),弱指针内部有个指针成员变量,指向shared_ptr类型,最终也是指向了shared_ptr的资源,但是这个不会使资源的引用计数增加,换句话说,weak_ptr可以像shared_ptr一样共享资源,也和shared_ptr一样可以获取资源的引用计数,但是weaak_ptr本身不会引起资源的引用计数的变化,只有shared_ptr才会。
使用示例及讲解:
int main()
{
//弱指针就是为了解决shared_ptr遗留的问题,循环引用问题,所以weak_ptr就是为了shared_ptr服务的
//弱指针的构造函数的形参是shared_ptr类型,所以弱指针拷贝构造必须用shared_ptr对象
int* p = new int(10);
shared_ptr<int>s_ptr1(p);
cout << "s_ptr1拥有的资源的引用计数:" << s_ptr1.use_count() << endl;
//weak_ptr w_ptr1 = s_ptr1;//OK,隐式拷贝构造ye可以
weak_ptr<int> w_ptr1(s_ptr1);
cout << "s_ptr1拥有的资源的引用计数:" << s_ptr1.use_count() << endl;
cout << "w_ptr1拥有的资源的引用计数:" << w_ptr1.use_count() << endl;
weak_ptr<int>w_ptr2(s_ptr1);
cout << "s_ptr1拥有的资源的引用计数:" << s_ptr1.use_count() << endl;
cout << "w_ptr2拥有的资源的引用计数:" << w_ptr2.use_count() << endl;
int* q = new int(100);
shared_ptr<int>s_ptr2;
weak_ptr<int>w_ptr3(w_ptr1);
cout << "w_ptr1拥有的资源的引用计数:" << w_ptr1.use_count() << endl;
cout << "w_ptr3拥有的资源的引用计数:" << w_ptr3.use_count() << endl;
s_ptr2 = s_ptr1;
cout << "s_ptr1拥有的资源的引用计数:" << s_ptr1.use_count() << endl;
cout << "s_ptr2拥有的资源的引用计数:" << w_ptr1.use_count() << endl;
cout << "w_ptr1拥有的资源的引用计数:" << w_ptr1.use_count() << endl;
cout << "w_ptr2拥有的资源的引用计数:" << w_ptr2.use_count() << endl;
cout << "w_ptr3拥有的资源的引用计数:" << w_ptr3.use_count() << endl;
shared_ptr<int>s_ptr3 = w_ptr1.lock();//将弱智能指针转换为强智能指针
cout << "s_ptr1拥有的资源的引用计数:" << s_ptr1.use_count() << endl;
cout << "s_ptr2拥有的资源的引用计数:" << w_ptr1.use_count() << endl;
cout << "w_ptr1拥有的资源的引用计数:" << w_ptr1.use_count() << endl;
cout << "w_ptr2拥有的资源的引用计数:" << w_ptr2.use_count() << endl;
cout << "w_ptr3拥有的资源的引用计数:" << w_ptr3.use_count() << endl;
return 0;
}
weak_ptr总结:
构造和赋值:
(1)为了解决shared_ptr解决不了的循环指向的问题,weak_ptr协助shared_ptr解决。即weak_ptr可以和shared_ptr之间通过强弱指针转换,拥有资源,而weak_ptr不会使资源的引用计数变化。
(2)weak_ptr是为了服务shared_ptr而诞生的,只允许用shared_ptr对其进行拷贝构造。
成员函数:
(1)reset()
(2)赋值运算符重载,允许用shared_ptr和weak_ptr赋值给weak_ptr,不会引起资源的引用计数的变化。
(3)lock()将弱指针转化为强指针(shared_ptr),被强指针接受,资源引用计数加加。前提是该弱指针内部成员指针变量指向的强指针还存在。
用weak_ptr解决shared_ptr的循环指向问题,上面问题由于对象a和对象b的引用计数都为2,导致没办法析构,让其中一个为1就好了,这样这个对象就会被释放,内部指针指向的另一个对象由于资源引用计数为1,另一个对象也会被释放。将A类或者B类中的一个shared_ptr修改为弱指针就好了。
代码1(将A类中的设置为弱指针):
class B;
class A
{
public:
A() {
cout << "A构造函数" << endl; }
~A() {
cout << "A析构函数" << endl; }
void fun(shared_ptr<B>& p) {
a_p = p; }
private:
weak_ptr<B> a_p;//注意这里是弱指针
};
class B
{
public:
B() {
cout << "B构造函数" << endl; }
~B() {
cout << "B析构函数" << endl; }
void fun(shared_ptr<A>& p) {
b_p = p; }
private:
shared_ptr<A> b_p;
};
int main()
{
A* a = new A();
B* b = new B();
shared_ptr<A>s_p1(a);
shared_ptr<B>s_p2(b);
cout <<"s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;
a->fun(s_p2);
b->fun(s_p1);
cout << "s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;
return 0;
}
图形分析:
注意weak_ptr< B >a_ptr指向shared_ptr< B >s_p2,这是因为weak_ptr内部是一个指针变量,指向的是shared_ptr类型的对象那个(在最后的仿写代码中可以看出来)。
s_p1和s_p2中,s_p2后构造的,先析构s_p2(栈中的对象,先构造的后析构,后构造的先析构),s_p2拥有资源b,资源b的引用计数为1,所以s_p2析构函数中对资源b进行释放,即delete b,b会调用析构函数析构,b对象里的b_ptr也会析构,b_ptr调用析构函数,由于b_ptr拥有资源a,资源a的引用计数为2,所以b_ptr析构函数里只是将资源a的引用计数减减,变为1;s_p1析构,拥有资源a,资源a引用计数为1,所以s_p1的析构函数中会对资源a进行释放,即delete a,所以会执行a的析构函数析构a,对象a中的a_ptr是弱指针,调用默认析构函数析构。
同理,如果是把B类中的shared_ptr指针设为弱指针的话,那么同样分析,就会调用a的析构函数,然后调用b的析构函数。下面将代码和运行结果和图形分析给出:
代码2(将类B中的shared_ptr设置为weak_ptr):
class B;
class A
{
public:
A() {
cout << "A构造函数" << endl; }
~A() {
cout << "A析构函数" << endl; }
void fun(shared_ptr<B>& p) {
a_p = p; }
private:
shared_ptr<B> a_p;
};
class B
{
public:
B() {
cout << "B构造函数" << endl; }
~B() {
cout << "B析构函数" << endl; }
void fun(shared_ptr<A>& p) {
b_p = p; }
private:
weak_ptr<A> b_p;//注意这里是弱指针
};
int main()
{
A* a = new A();
B* b = new B();
shared_ptr<A>s_p1(a);
shared_ptr<B>s_p2(b);
cout <<"s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;
a->fun(s_p2);
b->fun(s_p1);
cout << "s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;
return 0;
}
运行结果(注意这里就变成先析构对象a了):
图形分析:
和代码1分析一样,s_p1和s_p2中,s_p2后构造的,先析构s_p2(栈中的对象,先构造的后析构,后构造的先析构),s_p2拥有资源b,引用计数为2,所以资源b引用计数减减,变为1。s_p1析构,拥有资源a,资源a引用计数为1,所以s_p1析构函数中对资源a进行释放,即delete a,a会调用析构函数析构,a对象里的a_ptr也会析构,由于a_ptr指向资源b,资源b引用计数为1,所以a_ptr析构函数中会对资源进行释放,即delete b,会调用b的析构函数,对象b被析构,对象b内的弱指针b_ptr会调用默认析构函数析构。
代码3(将类A和类B中的shared_ptr都设置为weak_ptr):
class B;
class A
{
public:
A() {
cout << "A构造函数" << endl; }
~A() {
cout << "A析构函数" << endl; }
void fun(shared_ptr<B>& p) {
a_p = p; }
private:
weak_ptr<B> a_p;//弱指针
};
class B
{
public:
B() {
cout << "B构造函数" << endl; }
~B() {
cout << "B析构函数" << endl; }
void fun(shared_ptr<A>& p) {
b_p = p; }
private:
weak_ptr<A> b_p;//弱指针
};
int main()
{
A* a = new A();
B* b = new B();
shared_ptr<A>s_p1(a);
shared_ptr<B>s_p2(b);
cout <<"s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;
a->fun(s_p2);
b->fun(s_p1);
cout << "s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;
return 0;
}
运行结果:
图形分析:
一样分析,这个比上面两种分析更简单,s_p2后构造的,先析构s_p2,s_p2拥有资源b,资源b引用计数为1,所以在s_p2析构函数中会对资源进行释放,即delete b,所以会执行b的析构函数,所以对象b中的b_ptr也会析构,调用默认析构函数析构。s_p1再析构,s_p1拥有资源a,资源a引用计数为1,所以在s_p1析构函数中会对资源进行释放,即delete a,所以会执行a的析构函数,所以对象a中的a_ptr也会析构,调用默认析构函数析构。
注意:weak_ptr对象不会对资源有任何操作,也不会让资源引用计数有变化,内部是一个成员指针变量指向shared_ptr对象。weak_ptr是用来解决shared_ptr产生的环形指向问题。
注意:lock()成员函数可以将弱指针转化为强指针,即用shared_ptr接收返回值,资源引用计数加加。只有将弱指针用lock()方法转换为强指针,才能进行对资源的操作。
#ifndef MWEAK_PTR_H
#define MWEAK_PTR_H
#include"mshared_ptr.h"
template<typename T>
class Mweak_ptr
{
public:
Mweak_ptr(Mshared_ptr<T>& s_p = NULL)
{
_s_p = &s_p;
}
Mshared_ptr<T> lock()//将弱指针转化为强指针
{
if (_s_p->use_count() == 0)//说明指向的对象没有资源,返回默认构造函数产生的对象
{
return Mshared_ptr<T>();
}
return *_s_p;
}
void reset()//只是将weak_ptr对象的_s_p置空,不对资源有任何操作。
{
_s_p = NULL;
}
int use_count()
{
return _s_p->use_count();
}
private:
Mshared_ptr<T>* _s_p;
};
#endif