目录
前言:
1 智能指针的发展历史
4 有关定制删除器
前言:
智能指针的引入,我们得先从异常开始说起,异常面临的一个窘境是new了多个对象,抛异常了会导致先new的对象没有析构,从而导致内存泄漏的问题,解决方法是使用RAII。
RAII是一种技术,英文的全称是:Resource Acquisition Is Initialization,是利用对象的声明周期来解决内存泄漏的一种技术。
就像这样:
template<class T>
class Smart_ptr
{
public:Smart_ptr(T* ptr):_ptr(ptr){}~Smart_ptr(){cout << "~Smart_ptr" << endl;}private:T* _ptr;
};
double Division(int a, int b)
{if (b == 0){throw string("除数为0");}else{return (double)a / (double)b;}
}
void Func()
{Smart_ptr<int> p1 = new int[1000];Smart_ptr<int> p2 = new int[1000];int a = 0, b = 0;cin >> a >> b;Division(a, b);}
int main()
{try{Func();}catch (const exception& e){cout << e.what() << endl;}catch (...){cout << "捕获到未知异常" << endl;}return 0;
}
之前的忧虑来源于第二个抛异常之后前面的对象不会析构的问题,那么现在用一个智能指针对象来接受指针,不仅起到了管理空间的作用,还可以调用原本的行为,比如* -> ,这里还没有重载。
上文有些许未介绍的,比如exception,是C++异常体系里面的基类,所以捕捉到了异常我们就可以用exception来捕捉,打印是用的what函数,就和前面提到的示例代码是一样的。
现在就对智能指针有了一个简单的理解了,现在我们就深入了解一下。
1 智能指针的发展历史
提到智能指针,我们首先应该想到的是C++98里面的auto_ptr,早在C++98就引入了智能指针,但是使用确实让人一言难尽,它的本质是转移控制权,所以会导致一个悬空,像move左值一样,导致原来的自定义对象变为空了,这就不是将亡值的施舍了,是活生生的夺舍:
就像文档里面提到的,推荐使用unique_ptr而不是auto_ptr,因为是指针,所以需要重载函数使得该类有指针的对应行为,比如* -> ,这里我们先使用,所在的头文件是memory:
int main()
{auto_ptr<string> s1(new string("aaaa"));auto_ptr<string> s2(s1);return 0;
}
当我们通过监视窗口可以看到:
执行了拷贝构造之后,s1就悬空了,相应的,s2获得了控制权,但是,属实有点鸡肋。
比如之后还要访问s1,那就报错了,反正呢,很多公司是禁止使用auto_ptr的。
那么呢,在03年的时候,引入了一个库boost,这个库是由C++委员会的部分工作人员搞的,在C++11里面引入的很多新特性都是在boost里面借鉴的,其中就涉及到了智能指针,在boost里面有三种智能指针,分别是scoped_ptr,shared_ptr,weak_ptr,其中也有引入数组,但是C++11并没有采纳。
在C++11引入智能指针的时候就借鉴了boost的智能指针,但是有一个指针改了一个名,scoped_ptr改成了unique_str,为什么改呢,咱也不知道,学就完事儿了。
所以今天的重点就是unique_str和shared_ptr和weak_ptr,其中shared_ptr是人人都要会的。
2 unique_ptr和shared_ptr的基本使用
对于使用方面都是很简单,咱们先看一个文档:
模板参数有一个D = 什么什么的,这时定制删除器,本文后面会介绍,现在先不急。
基本的函数使用就是:
这就是所有的成员函数了,get函数是获取原生指针,release是显式的释放指针,但不是显式的析构这个类,同样的,既然是指针,就应该具备指针的行为,比如* ->等,有了stl的基本,这些我们应该是看一下文档就应该知道怎么使用的,这里再看看构造函数:
注意的是unique_ptr是不支持拷贝构造的,重载的第9个函数,拷贝函数被delete修饰了,所以不支持。其中支持auto_ptr 右值构造什么的,咱们先不管,主要了解前面几个就可以了。
class A
{
public:A(int a1 = 1, int a2 = 1):_a1(a1),_a2(a2){}~A(){cout << "~A()" << endl;}
//private:int _a1 = 1;int _a2 = 1;
};int main()
{unique_ptr<A> sp1(new A[10]);unique_ptr<A> sp2(sp1);A* p = sp1.get();cout << p << endl;sp1->_a1++;sp1->_a2++;return 0;
}
找找错误?
第一个 不能拷贝构造,第二析构会报错,因为开辟的是一个数组,基本类型是A,应该是A[],这就和定制删除器有关系了,所以这里的正确代码是:
class A
{
public:A(int a1 = 1, int a2 = 1):_a1(a1),_a2(a2){}~A(){cout << "~A()" << endl;}
//private:int _a1 = 1;int _a2 = 1;
};int main()
{unique_ptr<A[]> sp1(new A[10]);//unique_ptr<A> sp2(sp1);A* p = sp1.get();cout << p << endl;//sp1->_a1++;//sp1->_a2++;sp1[1]._a1++;sp1[1]._a2++;return 0;
}
这里的执行结果就是析构了十次:
为什么new了十个空间,基本类型也要是一个数组,这里的解决方案是定制删除器,先不管。
然后就是shared_ptr的基本使用:
相比之下shared_ptr就朴实无华很多了。
成员函数这么多,这里和pair有点像的是,有一个make_shared的东西,在构造的时候会用到:
因为它支持拷贝构造,欸~所以我们可以使用make_shared构造,产生临时对象拷贝一个就行。
简单介绍一下其中的成员函数,use_count是用来计算计数的,因为在智能指针要实现的一个事就是,管理资源更加简单,比如多个对象共同管理一块资源,新加了一个对象管理资源,引入计数就++,反之--,对比auto那里不能拷贝,就是因为如果拷贝了,造成了多次析构的问题,就会报错。
那么要验证计数很简单:
int main()
{//shared_ptr<int> sp1(new int);shared_ptr<int> sp1 = make_shared<int>(1);cout << sp1.use_count() << endl;{shared_ptr<int> sp2(sp1);cout << sp2.use_count() << endl;}cout << sp1.use_count() << endl;return 0;
}
既然是出了局部域就会销毁,那么我们创建一个局部域即可:
简单使用了解一下可以了,咱们这里简单模拟实现一下。
3 shared_ptr的模拟实现
目前对于shared_ptr我们的简单理解有了,那么现在简单捋一下,智能指针防止多次析构的方法是使用计数的方式,那么我们就需要一个计数了,问题是这个变量我们如何创建呢?
template<class T>
class shared_ptr
{
private:T* ptr;int count;
};
这样可行吗?显然是不可行的,因为我们创建一个对象,指向空间,就会让这个类多次实例化,每个对象都有一个count,实例化一次count++,然后呢就导致每个对象都有count,count的都是一样的,我们指向一个空间,创建了n个对象,预期是计数为n,但是创建的对象是独立的,析构只会析构某个对象的count,从而count的大小是n * n,所以结果是不可预估的。
那么我们应该用static吗?每创建一个对象就++一次,看起来好像可以,但是我们如果指向的空间不是一个呢?new了两个空间,就会导致两个空间公用一个计数,更不行了。
所以解决办法是创建一个指针,每创建一个对象,指针指向的空间,即计数空间就++:
template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr):_ptr(ptr), _pcount(new int(1)){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){(*_pcount)++;}int use_count(){return *_pcount;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;
};
这是一般的函数,重点在于赋值重载 和 定制删除器。
对于赋值重载来说,指向的空间修正,指向的计数空间修正,那么原来指向的空间是否需要修正呢?这就要看计数是否到0了,所以需要判断是否到0,到了就析构:
那么析构函数,简单的,new了空间删除就完事了:
void release()
{if (--(*_pcount) == 0){delete _ptr;delete _pcount;}
}
~shared_ptr()
{release();
}
赋值重载判断+ 改定指向:
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{if (--(sp._pcount) == 0){release();_ptr = sp._ptr;_pcount = sp._pcount;(*_pcount)++;}return *this;
}
但是,万一有人突发奇想,想自己给自己赋值呢?再万一,有人在指向空间是同一块的基础上相互赋值呢?
所以我们不妨判断一下指向空间的地址是不是一样的:
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcount = sp._pcount;(*_pcount)++;}return *this;
}
shared_ptr的基本实现就完成了。
现在引入一个线程安全的问题:
mutex mtx;
void Func(Ptr::shared_ptr<list<int>> sp, int n)
{cout << sp.use_count() << endl;for (int i = 0; i < n; i++){Ptr::shared_ptr<list<int>> copy1(sp);Ptr::shared_ptr<list<int>> copy2(sp);Ptr::shared_ptr<list<int>> copy3(sp);mtx.lock();sp->push_back(i); mtx.unlock();}
}int main()
{Ptr::shared_ptr<list<int>> sp1(new list<int>);cout << sp1.use_count() << endl;thread t1(Func, sp1, 100);thread t2(Func, sp1, 200);t1.join();t2.join();cout << sp1->size() << endl;cout << sp1.use_count() << endl;return 0;
}
解决方法是引入atomic,原子操作:
shared_ptr(T* ptr):_ptr(ptr), _pcount(new std::atomic<int>(1))
{}private:T* _ptr;std::atomic<int>* _pcount;
还有一个问题就是,如果是交叉指向,就会导致无法析构:
struct Node
{//std::shared_ptr<Node> _next;//std::shared_ptr<Node> _prev;std::weak_ptr<Node> _next;std::weak_ptr<Node> _prev;int _val;~Node(){cout << "~Node()" << endl;}
};
int main()
{std::shared_ptr<Node> p1(new Node);std::shared_ptr<Node> p2(new Node);cout << p1.use_count() << endl;cout << p2.use_count() << endl;p1->_next = p2;p2->_prev = p1;cout << p1.use_count() << endl;cout << p2.use_count() << endl;cout << p1->_next.use_count() << endl;cout << p2->_prev.use_count() << endl;return 0;
}
此时就需要弱指针,weak_ptr,只是指向空间,但是计数不++即可。
4 有关定制删除器
为什么会引入定制删除器呢?
int main()
{Ptr::shared_ptr<int> sp1((int*)malloc(sizeof(4)));Ptr::shared_ptr<FILE> sp2(fopen("test.txt", "w"));return 0;
}
在内存管理章节提及,内存管理的函数不能混合使用,何况是FILE呢,FILE*要使用fclose,所以我们应该定制一个删除器,但是如何传过去呢?传只能传到构造,传不到析构,所以我们不妨:
template<class D>
shared_ptr(T* ptr, D del): _ptr(ptr), _pcount(new std::atomic<int>(1)), _del(del)
{}private:T* _ptr;std::atomic<int>* _pcount;std::function<void(T*)> _del = [](T* ptr) {delete ptr; };
要删除的时候,传对象就可以了,比如:
template<class T>
struct FreeFunc {void operator()(T* ptr){cout << "free:" << ptr << endl;free(ptr);}
};
class A
{
public:A(int a1 = 1, int a2 = 1):_a1(a1),_a2(a2){}~A(){cout << "~A()" << endl;}
private:int _a1 = 1;int _a2 = 1;
};int main()
{Ptr::shared_ptr<A> sp1(new A[10], [](A* ptr) {delete[] ptr; });Ptr::shared_ptr<int> sp2((int*)malloc(4), FreeFunc<int>());Ptr::shared_ptr<FILE> sp3(fopen("test.txt", "w"), [](FILE* ptr) {fclose(ptr); });Ptr::shared_ptr<A> sp4(new A);return 0;
}
智能指针就介绍到这里。
感谢阅读!