pointer or function - like classes[仿指针与仿函数]
首先说明一下仿指针与仿函数是什么,指针我们知道是一个地址,我们可以利用指针来访问它所指向的value,仿指针就是用一个类去实现指针的作用,那么我们为什么要特地写一个类去实现指针呢,因为在我们开发过程中,我们希望指针可以有更多功能,而不仅仅是指向一个地址,那么我们就可以通过一个类去实现指针的功能的前提下,给它加上更多的功能,来满足我们的需求,比如说我们每使用一次指针,都要去释放掉这个指针,如果忘记释放,就会造成内存泄露,但我们无法使指针自己释放,但是如果是指针类,我们就可以在析构函数中加上delete语句,那么以后使用这个指针,就再也不用管它的释放问题了,C++特性中的智能指针其实就是这种原理,仿函数也是类似道理。
_ pointer-like classes
简单用代码实现一个智能指针,其实就是老版本的shared_ptr指针,可以明白其中的运作原理
#include<iostream> using namespace std; template<class T> class shared_ptrc { public: T& operator*() const { return *px; } T* operator->() const { return px; } shared_ptrc(T* p) : px(p){} ~shared_ptrc() { delete px; } private: T* px; long* pn; }; struct Foo { void method() { cout << "Foo::method" << endl; } }; int main() { shared_ptrc<Foo> sp(new Foo); Foo f(*sp); sp->method(); system("pause"); return 0; }
首先从它的重载说起, 可以看到
T& operator*() const { return *px; }
重载了*号,并且无参数,这样就符合了指针的语法,我们平常建立一个指针比如int * p; *p代表就是它所指的value,再看这里,返回 *px,而px又是T类的指针(T * px),所以这里就是返回一个value,并且函数开头是T&,
之前说了返回value的reference比直接返回value普遍要快很多,所以这里返回了一个reference。
我们再看
T* operator->() const { return px; }
当我们使用"->"时,是不是一般都是调用指针所指内容的方法或者属性,所以我们必须要返回指针类型,所以这里是 T* operator->() ,(如果这里不明白可以看之前说的引用与指针的区别)
所以现在来看,我们是不是就可以把shared_ptrc当作一个指针类型来使用了
shared_ptrc<Foo> sp(new Foo);
这样就是导入Foo模板,并且new一个Foo类对象,让shared_ptrc类中的px指针指向它,然后当我们使用完这个指针后,程序结束后,它会调用析构函数自动销毁,无需手动释放,这就是仿指针的基本用法。
迭代器其实也是一种仿指针,平常使用迭代器我们都有接触过,比如创建一个vector数组的迭代器,vector<int>::iterator it;我们经常会使用这种语法 it++,++it,使迭代器指针指向数组中的下一个内容,这其实就是在指针类中重载了++符号来实现的。
为了更加了解仿指针,我们可以看一看C++的智能指针,这样也能使我们对使用指针的风险更加了解。
有4种智能指针:auto_ptr(C++11已弃用), unique_ptr,shared_ptr, weak_ptr
首先看看智能指针为我们解决的问题,其实跟上面所说的基本一样。
当我们使用普通指针时:
void func(string & str) { ... string * s = new string(str); if (error()) throw exception(); str = *s; delete s; ... return; }
当程序出错,抛出异常,可以很明显看到发生了一个严重的问题,s指针未被释放,程序就中止了,导致内存泄露。
而我们如果使用智能指针auto_ptr
void func(string & str) { ... auto_ptr<string> s(new string(str)); if (error()) throw exception(); str = *s; ... return; }
程序抛出异常,auto_ptr会调用内部析构函数,释放掉指针,这样就避免了内存泄漏。
然后说一下为什么auto_ptr会被抛弃,auto_ptr的工作模式是拥有所有权。对于特定的对象,只能有一个智能指针可拥有,这样只有拥有对象的智能指针的析构函数会删除该对象。赋值操作会转让所有权。
可以看下面这一串代码:
#include <iostream> #include <string> using namespace std; int main() { auto_ptr<string> test[3] = { auto_ptr<string> (new string("helloworld1")), auto_ptr<string> (new string("helloworld2")), auto_ptr<string> (new string("helloworld3")), }; auto_ptr<string> p; p = test[2]; // test[2]所有权被p拿走,此时test[2]为一个空指针。 cout<<test[2]<<endl; //运行时报错 return 0; }
可以看出auto_ptr存在内存泄漏的潜在风险,所以不再使用auto_ptr,unique_ptr也是所有权模式,但是如果将上面auto_ptr换成unique_ptr的话,不会在运行程序时报错,而是会在编译时报错,这样也可以避免内存泄漏的潜在风险。
shared_ptr与weak_ptr则是另外一种模式,这里就不详细说明了,以后专门再用一篇博客描述4种智能指针。
_ function-like classes
仿函数也是用一个模板类去实现的,实现原理与仿指针差不多,由于目前不经常使用,了解不是很深,就先把代码语法问题解决了,以后再补充说明仿函数的使用环境与时机。
template <class T> struct identity{ const T& operator() (const T& x) const {return x;} }; template <class Pair> struct select1st { const typename Pair::first_type& operator() (const Pair& x) const { return x.first;} }; template <class Pair> struct select2nd { const typename Pair::second_type& operator() (const Pair& x) const { return x.second;} }; template <class T1,class T2> struct pair{ T1 first; T2 second; pair() : first(T1()),second(T2()){} pair(const T1& a, const T2& b) : first(a),second(b) {} ... };
可以看到select1st与select2nd可以当作函数来使用,分别返回pair的两个value。