赞
踩
目录
由于C++没有GC(垃圾回收器),程序员从堆上申请的资源,打开的文件,创建的套接字需要我们手动释放和关闭。
但是可能会出现两个问题:
这样就会导致资源的泄漏。计算机的资源是有限的,导致我们可以使用的资源越来越少。
智能指针是针对我们从堆上申请的空间,比如:new和malloc出来的空间。使得智能指针来帮我们释放空间,不需要我们来手动释放了。
RAII是一种了利用对象生命周期来控制程序资源(如:内存,文件,套接字,互斥量等)的技术。
在对象构造时获得资源,使得堆资源的控制在对象的生命周期内都有效,最后在对象析构的时候释放资源。
实际上我们是将资源的管理托管给了一个对象。这样做有两个好处:
利用RAII实现一个最简单的智能指针。
- #pragma once
-
- #include<iostream>
-
- using namespace std;
- template<class T>
- class SmatrPrt{
- private:
- T* _ptr;
- public:
- //将外面申请的资源,托管给类的成员
- SmatrPrt(T* ptr = nullptr)
- :_ptr(ptr)
- {}
-
- //在对象析构时,自动释放资源
- ~SmatrPrt(){
- if (_ptr){
- cout << "delete ptr" << endl;
- delete _ptr;
- }
-
- }
- };
- #include "SmartPtr.h"
-
- void test(){
- //将申请的资源托管给了sp对象
- //当对象空间释放会调用析构函数,释放资源
- SmatrPrt<int> sp(new int);
- }
-
-
- int main(){
-
- test();
-
- system("pause");
- return 0;
- }

智能指针的原理利用了RAII思想,并且还需要让智能指针具有指针的功能。即,需要重载operator*和operator->的函数。
之后在使用指针时,只需要像指针一样控制对象即可。
- #pragma once
-
- #include<iostream>
-
- using namespace std;
- template<class T>
- class SmatrPrt{
- private:
- T* _ptr;
- public:
- //将外面申请的资源,托管给类的成员
- SmatrPrt(T* ptr = nullptr)
- :_ptr(ptr)
- {}
-
- T& operator*(){
- return *_ptr;
- }
- T* operator->(){
- return _ptr;
- }
-
-
- //在对象析构时,自动释放资源
- ~SmatrPrt(){
- if (_ptr){
- cout << "delete ptr" << endl;
- delete _ptr;
- }
-
- }
- };
-
- #include "SmartPtr.h"
-
- void test(){
- SmatrPrt<int> sp(new int);
- //像指针一样操作对象
- *sp = 20;
- cout << *sp << endl;
- }
-
-
- int main(){
-
- test();
-
- system("pause");
- return 0;
- }

总结指针指针:
上面的代码有一个bug:
原因:
用sp1拷贝构造sp2,由于是浅拷贝。会使得两个对象的成员变量指向同一块空间。两个对象在析构时,导致一块空间释放了两次。所以程序会奔溃。
随着C++的发展,有三个解决方案,一个方案对应着一种智能指针。
注意:智能指针都包含在memory的库中,要使用智能指针必须包含这个库。
auto_ptr的原理是:将资源的管理权由一个对象转移给另外一个对象。
但是这样会有一个问题,如果重新访问ap1,就会出现问题。这种方式在实际在编程中用得少。
模拟实现auto_ptr:
注意:赋值防止自己给自己赋值,还需要释放当前对象以前的资源。
- template<class T>
- class AutoPtr{
- private:
- T* _ptr;
- public:
-
- AutoPtr(T* ptr = nullptr)
- :_ptr(ptr)
- {}
-
- //拷贝构造
- //将ap资源的管理权交当前对象
- AutoPtr(const AutoPtr<T>& ap){
- _ptr = ap._ptr;
- ap._ptr = nullptr;
- }
-
- AutoPtr<T>& operator=(const AutoPtr<T>& ap){
- //防止自己给自己赋值
- if (this != &ap){
- //释放之前的资源
- if (_ptr){
- delete _ptr;
- }
-
- _ptr = ap._ptr;
- ap._ptr = nullptr;
- }
- return *this;
- }
-
- T& operator*(){
- return *_ptr;
- }
- T* operator->(){
- return _ptr;
- }
-
-
- //在对象析构时,自动释放资源
- ~AutoPtr(){
- if (_ptr){
- cout << "delete ptr" << endl;
- delete _ptr;
- }
-
- }
- };

针对auto_ptr的不足,C++11根据自己的一些语法,设计了一种更靠谱的智能指针,unique_ptr。
原理:直接简单粗暴,将构造和拷贝构造直接禁止编译器默认生成。
unique_ptr模拟实现:
- #pragma once
-
- #include<iostream>
-
- using namespace std;
-
- template<class T>
- class UniquePtr{
- private:
- //C++98,将拷贝构造和赋值,显示声明,设为私有
- //外部无法调用
- UniquePtr(UniquePtr<T>& up);
- UniquePtr<T>& operator=(UniquePtr<T>& up);
-
-
- T* _ptr;
- public:
- //将外面申请的资源,托管给类的成员
- UniquePtr(T* ptr = nullptr)
- :_ptr(ptr)
- {}
-
- //利用C++11语法,禁止编译器默认生成
- UniquePtr(UniquePtr<T>& up) = delete;
- UniquePtr<T>& operator=(UniquePtr<T>& up) = delete;
-
-
- T& operator*(){
- return *_ptr;
- }
- T* operator->(){
- return _ptr;
- }
-
-
- //在对象析构时,自动释放资源
- ~UniquePtr(){
- if (_ptr){
- cout << "delete ptr" << endl;
- delete _ptr;
- }
-
- }
- };

shared_ptr原理:在类中增加一个成员变量用来计数。每次调用一次拷贝构造和赋值重载函数,即每增加一个管理者,计数加1。没析构一个管理者对象,计数器减1,直到减为0,才真正释放资源。
注意:
shared_ptr只有在构造和赋值的时候计数才会加1。
计数器如何设置:
由于当前计数器属于同一资源的对象,可以将计数器设计成一个指针。
不能设计成引用,引用要引用外部变量,需要外部管理。也不能设计成静态成员变量,静态成员变量,属于整个类,如果当前类要同时管理其它资源计数器就乱了。
线程安全问题:
由于引用了一个计数器。计数器自增(++),自减(--)和判断的行为不是原子的。当多个线程进入,会导致线程安全问题。从而导致资源没有释放,造成内存泄漏。
为了保证线程安全,可以在shared_ptr类中,再增加一把锁。由于同一份资源的对象要看到同意把锁,所以,锁也需要声明成指针类型。
shared_ptr模拟实现:
注意:
shared_ptr缺陷:
循环引用问题:
一种情况:
当循环引用时,shared_ptr会出现资源没有释放的问题。
解决方案:
C++11增加了一个weak_ptr专门来解决shared_ptr循环引用的问题。
原理是:weak_ptr的成员变量是shared_ptr,不在具有RAII的思想,只是具有指针的功能,也就是,不会释放资源,也不会将计数器计数。
使用weak_ptr前提是,知道是循环引用了。
- template<class T>
- class WeakPtr{
- private:
- T* _wptr;
- public:
-
- WeakPtr(T* wptr = nullptr){
- _wptr = wptr;
- }
- //用shared_ptr构造
- WeakPtr(const SharedPtr<T>& sp){
- _wptr = sp.GetPtr();
- }
-
- WeakPtr<T>& operator=(const SharedPtr<T>& sp){
- _wptr = sp.GetPtr();
- return *this;
- }
- //具有指针的功能
- T* operator->(){
- return &_wptr
- }
-
- T& operator*(){
- return *_wptr;
- }
- };

上面问题的改造:
删除器:
shared_ptr类里默认的在析构函数里释放资源使用的是delete。但是当我们申请出来的不是一个对象,而是多个对象时:
于是C++11增加了一个删除器,可以根据申请对象的不同,设计删除动作,传入shared_ptr中。
实际用法是,写一个仿函数类(写一个类,从在operator()函数,函数实现是需要删除资源的动作),实例化对象,传入shared_ptr中。
- template<class T>
- class Del{
- public:
- void operator()(T* ptr){
- delete[] ptr;
- }
- };
-
-
- class DelFle{
- public:
- void operator()(FILE* ptr){
- fclose(ptr);
- }
- };
-
- void test(){
- Del<int> d;
- shared_ptr<int> sp1(new int[10], d);
-
- DelFle df;
- shared_ptr<FILE> sp2(fopen("text.txt", "w"), df);
-
-
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。