赞
踩
目录
回顾上一篇章,学习了异常机制,但面临了一种情况还没有解决,就是异常带来的内存泄漏,如下:
- #include <iostream>
- using namespace std;
-
-
- double Division(int a, int b)
- {
- // 当b == 0时抛出异常
- if (b == 0)
- throw "Division by zero condition!";//抛出的异常是字符串
- else
- return ((double)a / (double)b);
- }
- void Func()
- {
- int* p = new int[1];
- int len, time;
- cin >> len >> time;
-
- cout << Division(len, time) << endl;
-
- delete[] p;
-
- }
-
- int main() {
- try
- {
- Func();
- }
- catch (const char* errmsg)
- {
- cout << "Caught in main: " << errmsg << endl;
- }
- return 0;
- }
调用Division函数时,若抛出异常,最终是被main函数中的catch所捕获,直接进入到main函数中的catch中了,但是,delete[] p不会被执行了 ,导致的问题就是内存泄漏。那么可以通过智能指针来解决这个问题
RAII是一种利用对象生命周期来控制程序资源的简单技术。在对象构造时获取对象资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候由编译器顺带释放资源。实际上把控资源就是管理对象。
这种做法体现的优势是:
- 不需要显示地释放资源
- 采用这种方式,对象所需的资源在其生命期内始终保持有效
RAII是一种设计思想,而智能指针正是采用了这种思想。
RAII思想设计:
- #include <iostream>
- using namespace std;
-
- // 使用RAII思想设计的SmartPtr类
- template<class T>
- class SmartPtr {
- public:
- SmartPtr(T* ptr = nullptr)
- :_ptr(ptr)
- {}
-
- ~SmartPtr()
- {
- if (_ptr)
- delete _ptr;
- }
-
- private:
- T* _ptr;
- };
- double Division(int a, int b)
- {
- // 当b == 0时抛出异常
- if (b == 0)
- throw "Division by zero condition!";//抛出的异常是字符串
- else
- return ((double)a / (double)b);
- }
- void Func()
- {
- int* p = new int[1];
- int len, time;
- cin >> len >> time;
-
- cout << Division(len, time) << endl;
-
- SmartPtr<int> sp(p);//当Division抛异常时,会被main函数中的catch捕获,那么程序会直接跳到main函数所在的catch
- //对此了,Func函数的栈帧会自动销毁,对于内置类型会自动释放,对于自定义类型而言会去调用它的析构函数
- //所以sp会去调用析构函数,同时释放了p的资源
-
- }
-
- int main() {
- try
- {
- Func();
- }
- catch (const char* errmsg)
- {
- cout << "Caught in main: " << errmsg << endl;
- }
- return 0;
- }
输出结果:
上述的SmartPtr虽然是通过RAII思想设计的类,但还不能真正称作为智能指针,因为它还不有指针的行为。指针可以解引用,也可以通过->去访问所指空间的内容,因此还需要重载*和->。
- #include <iostream>
- using namespace std;
- template<class T>
- class SmartPtr {
- public:
- SmartPtr(T* ptr = nullptr)
- : _ptr(ptr)
- {}
- ~SmartPtr()
- {
- if (_ptr)
- delete _ptr;
- }
-
- T& operator*()
- {
- return *_ptr;
- }
- T* operator->()
- {
- return _ptr;
- }
- private:
- T* _ptr;
- };
- struct Date
- {
- int _year;
- int _month;
- int _day;
- };
- int main()
- {
- SmartPtr<int> sp1(new int);
- *sp1 = 10;
- cout << *sp1 << endl;
-
- SmartPtr<Date> sparray(new Date);
-
- sparray->_year = 2024;
- sparray->_month = 7;
- sparray->_day = 7;
-
- cout << (*sparray)._year << ":" << (*sparray)._month << ":" << (*sparray)._day << endl;
-
- return 0;
- }
输出结果:
总结一下,智能指针是通过RAII思想设计出的类,它重载operator*和operator->,具有像指针一样的行为。
对于上述代码,却有一个弊端,如下:
- int main()
- {
- SmartPtr<int> sp1(new int);
- SmartPtr<int> sp2(sp1);
-
- return 0;
- }
带来的问题是,sp1拷贝给sp2是一个浅拷贝,所以sp2._ptr和sp1._ptr指向同一块空间,在析构时候,sp2._ptr所指向空间先释放,随后释放sp1._ptr所指向空间,但是该空间已被释放,所以会报错。那么为了解决这个问题,在历史上,智能指针有了一定的发展,来看。
C++98版本库中提供了auto_ptr的智能指针。他上述问题的实现原理是:通过管理权转移思想。
- // C++98 管理权转移 auto_ptr
- #include <iostream>
- using namespace std;
- namespace bit
- {
- template<class T>
- class auto_ptr
- {
- public:
- auto_ptr(T* ptr)
- :_ptr(ptr)
- {}
- //ap2(ap1)
- auto_ptr(auto_ptr<T>& sp)
- :_ptr(sp._ptr)
- {
- // 管理权转移
- sp._ptr = nullptr;//相当于ap1._ptr不再管理资源了,被置空了
- }
-
-
- //ap2= ap1
- auto_ptr<T>& operator=(auto_ptr<T>& ap)
- {
- // 检测是否为自己给自己赋值
- if (this != &ap)
- {
- // 释放当前对象中资源
- if (_ptr)
- delete _ptr;
- // 转移ap中资源到当前对象中
- _ptr = ap._ptr;
- ap._ptr = NULL;
- }
- return *this;
- }
- ~auto_ptr()
- {
- if (_ptr)
- {
- cout << "delete:" << _ptr << endl;
- delete _ptr;
- }
- }
- // 像指针一样使用
- T& operator*()
- {
- return *_ptr;
- }
- T* operator->()
- {
- return _ptr;
- }
- private:
- T* _ptr;
- };
- }
-
- int main()
- {
- std::auto_ptr<int> sp1(new int);
- std::auto_ptr<int> sp2(sp1); // 管理权转移,sp1不再管理资源了,在析构的时候,确实没啥问题
- //但带来了另一个问题,sp1所管理资源,被置空了,即sp1._ptr1 == nullptr,但对于外人来说并不知道
- //若有人使用已被释放的资源,如下:
- *sp2 = 10;
- cout << *sp2 << endl;
- cout << *sp1 << endl;//对空指针解引用,报错
- return 0;
- }
auto_ptr指针虽然解决了析构带来的问题,但是带来了新的问题,采用管理权转移思想,将资源由最后一个拷贝对象管理,而被拷贝对象都被置空了,导致有人使用被置空的拷贝对象时,会出现对空指针解引用,报错。所以该智能指针也可以算是一个"失败的man",很多公司也明确要求不能使用它。
随着C++的发展,C++11提供了更靠谱的智能指针unique_ptr,其实现原理:简单粗暴的防拷贝。
- #include <iostream>
- using namespace std;
- namespace bit
- {
- template<class T>
- class unique_ptr
- {
- public:
- unique_ptr(T* ptr)
- :_ptr(ptr)
- {}
-
- ~unique_ptr()
- {
- if (_ptr)
- {
- cout << "delete:" << _ptr << endl;
- delete _ptr;
- }
- }
-
- // 像指针一样使用
- T& operator*()
- {
- return *_ptr;
- }
-
- T* operator->()
- {
- return _ptr;
- }
-
-
- //只声明不实现,但是不能防止有人在类外实现,所以此方法不行
- //unique_ptr(unique_ptr<T>& sp);
-
-
- //c++11仿拷贝
- /*unique_ptr(const unique_ptr<T>& sp) = delete;
- unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;*/
- private:
- T* _ptr;
-
- //将拷贝私有化可以防拷贝,但这是c++98用法
- //unique_ptr(unique_ptr<T>& sp);
-
- };
- }
-
- //类外实现拷贝
- //template<class T>
- //bit::unique_ptr<T>::unique_ptr(bit:: unique_ptr<T>& sp)
- //{
- // //...
- //}
-
- int main()
- {
- bit::unique_ptr<int> sp1(new int);
- //bit::unique_ptr<int> sp2(sp1);//拷贝构造被删除了,编译器也不会默认生成了
-
- return 0;
- }
由于前面出现的问题都是因为拷贝带来的,那么unique_ptr了就直接把拷贝给禁了,不让你因为各种操作而导致额外的问题,这确实解决了。但是不能使用拷贝了,这也不是办法呀
随着前面智能指针问题的暴露,C++11不断发展,最终也完善了该问题,提出了更靠谱且支持拷贝的shared_ptr。其原理:是通过引用计数方式来实现多个shared_ptr对象之间共享资源。
其满足以下规则:
1.每个shared_ptr对象,其都包含着一个指针,该指针所指向空间内容用来计数有多少个指针指向该空间,即有多少个对象管理同一份资源,当多个对象管理同一份资源,那么他们的指针就会指向同一份空间,空间内容记录着这些数量,每增加一个对象管理相同资源,则计数也会加1
2.当对象销毁时即调用析构函数,就说明该对象不再管理这份资源,其对应的指针不再指向计数的空间,引用计数就会减1
3.如果引用计数是0,就说明没有对象管理该资源了,则必须释放该资源,相反地,如果不是0,说明还有对象管理资源,则就不能释放该资源,否则,对于其他对象中的指针就成野指针了。
- #include <iostream>
- using namespace std;
-
- namespace bit
- {
- template<class T>
- class shared_ptr
- {
- public:
- shared_ptr(T* ptr = nullptr)
- :_ptr(ptr)
- , _pcount(new int(1))
- {}
-
- shared_ptr(const shared_ptr<T>& sp)
- {
- if (this != &sp)
- {
-
- _ptr = sp._ptr;
-
- _pcount = sp._pcount;
- (*_pcount)++;
- }
- }
-
- shared_ptr<T>& operator=(const shared_ptr<T>& sp)
- {
- if (this != &sp)
- {
-
- if (--(*sp._pcount) == 0)
- {
- delete _ptr;
- delete _pcount;
- }
- _ptr = sp._ptr;
- _pcount = sp._pcount;
- (*_pcount)++;
- }
- return *this;
- }
-
- ~shared_ptr()
- {
- if (--(*_pcount) == 0)
- {
- delete _ptr;
- delete _pcount;
-
- _ptr = nullptr;
- _pcount = nullptr;
- }
- }
-
- T& operator*()
- {
- return *_ptr;
- }
-
- T* operator->()
- {
- return _ptr;
- }
-
- private:
- T* _ptr;
- int* _pcount;
-
- };
- }
调试结果:
shared_ptr解决了拷贝带来的问题,不幸的是,又出来了新的问题,那就是在线程安全和循环引用问题,对于线程问题,先放放,那我们要讲的是循环引用问题。
根据上述shared_ptr的代码,在外头定义一个类,进行以下操作:
- struct ListNode
- {
- bit::shared_ptr<ListNode> _next;
- bit::shared_ptr<ListNode> _prev;
-
- ~ListNode()
- {
- cout << "~ListNode()" << endl;
- }
- };
- int main()
- {
- bit::shared_ptr<ListNode> node1(new ListNode);
- bit::shared_ptr<ListNode> node2(new ListNode);
-
- node1->_next = node2;
- node2->_prev = node1;
-
-
- return 0;
- }
输出结果:
没有任何结果,而当屏蔽node1->_next = node2;或者node2->_prev = node1;中的任何一句,
其输出结果:
这是为何?如图:
两边的节点各自受到_next,_prev牵连,造成了一个循环,且引用计数一直维持在1,并没有减到0,只要屏蔽其中一条语句就不会受到限制,正常析构。虽然这个智能指针是模拟的,就算是库里面的也有一样的问题。
那么为了解决该问题,专门引入了一个智能指针来处理--weak_ptr
- #include <iostream>
- using namespace std;
-
- namespace bit
- {
- template<class T>
- class shared_ptr
- {
- public:
- shared_ptr(T* ptr = nullptr)
- :_ptr(ptr)
- , _pcount(new int(1))
- {}
-
- shared_ptr(const shared_ptr<T>& sp)
- {
- if (this != &sp)
- {
-
- _ptr = sp._ptr;
-
- _pcount = sp._pcount;
- ++(*_pcount);
- }
- }
-
- shared_ptr<T>& operator=(const shared_ptr<T>& sp)
- {
- if (this != &sp)
- {
-
- if (--(*_pcount) == 0)
- {
- delete _ptr;
- delete _pcount;
- }
- _ptr = sp._ptr;
- _pcount = sp._pcount;
- ++(*_pcount);
- }
- return *this;
- }
-
- ~shared_ptr()
- {
- if (--(*_pcount) == 0)
- {
- delete _ptr;
- delete _pcount;
-
- _ptr = nullptr;
- _pcount = nullptr;
- }
- }
-
- T& operator*()
- {
- return *_ptr;
- }
-
- T* operator->()
- {
- return _ptr;
- }
-
- T* get() const
- {
- return _ptr;
- }
- private:
- T* _ptr;
- int* _pcount;
-
- };
-
- // 简化版本的weak_ptr实现
- template<class T>
- class weak_ptr
- {
- public:
- weak_ptr()
- :_ptr(nullptr)
- {}
- weak_ptr(const shared_ptr<T>& sp)
- :_ptr(sp.get())
- {}
- weak_ptr<T>& operator=(const shared_ptr<T>& sp)
- {
- _ptr = sp.get();
- return *this;
- }
- T& operator*()
- {
- return *_ptr;
- }
- T* operator->()
- {
- return _ptr;
- }
- private:
- T* _ptr;
- };
-
- }
-
-
- struct ListNode
- {
- bit::weak_ptr<ListNode> _next;
- bit::weak_ptr<ListNode> _prev;
-
- ~ListNode()
- {
- cout << "~ListNode()" << endl;
- }
- };
-
-
- int main()
- {
- bit::shared_ptr<ListNode> node1(new ListNode);
- bit::shared_ptr<ListNode> node2(new ListNode);
-
-
-
- node1->_next = node2;
- node2->_prev = node1;
-
-
- return 0;
- }
输出结果:
那么weak_ptr是怎样解决问题的了,其实很简单,weak_ptr不在对引用计数进行管理了,所以shared_ptr对象初始化时的引用计数都是为1,在进行析构的时候,引用计数都会减到0,然后释放对应的_ptr,_pcount。
对于智能指针的析构还有一个问题,就是对于不是通过new出来的对象或者说new出来的对象是带有[]的如何通过智能指针进行管理,总不能每一种情况来一份代码。那么shared_ptr设计了一个删除器来解决这个问题。
- // 仿函数的删除器
-
- #include <iostream>
- using namespace std;
-
- template<class T>
- struct FreeFunc {
- void operator()(T* ptr)
- {
- cout << "free:" << ptr << endl;
- free(ptr);
- }
- };
- template<class T>
- struct DeleteArrayFunc {
- void operator()(T* ptr)
- {
- cout << "delete[]" << ptr << endl;
- delete[] ptr;
- }
- };
- int main()
- {
- FreeFunc<int> freeFunc;
- shared_ptr<int> sp1((int*)malloc(4), freeFunc);
-
-
- DeleteArrayFunc<int> deleteArrayFunc;
- shared_ptr<int> sp2(new int[4], deleteArrayFunc);
-
-
- shared_ptr<FILE> sp5(fopen("test.txt", "w"), [](FILE* p)
- {fclose(p); });
-
- return 0;
- }
输出结果:
如上代码,删除器可以通过自己定义的方式来释放空间,这只是展示了仿函数删除器,仅仅是九牛一毛。
Boost库是为C++语言标准库提供扩展的一些C++程序库的总称,是一个"准"标准库,由Boost社区组织开发、维护。Boost库可以与C++标准库完美共同工作,并且为其提供扩展功能。
1.C++98 中产生了第一个智能指针auto ptr.
不好的设计,对象悬空(不建议使用)
2.C++ boost给出了更实用的scoped ptr和shared ptr和weak ptr.
scoped ptr 防拷贝-》简单粗暴,对于不需要拷贝的场景非常好
shared ptr 引用计数,最后一个释放的对象释放资源 -》复杂一些,但是支持拷贝,非常完美-》问题:循环引用
weak ptr 解决循环引用,不参与引用计数
3.C++ TR1,引入了shared ptr等。不过注意的是TR1并不是标准版,
4.C++ 11,引入了unique_ptr和shared ptr和weak_ptr。需要注意的是unique_ptr对应boost 的scoped ptr。并且这些智能指针的实现原理是参考boost中的实现的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。