C++仿函数与仿指针

C++仿函数与仿指针

首先说明一下仿指针与仿函数是什么,指针我们知道是一个地址,我们可以利用指针来访问它所指向的value,仿指针就是用一个类去实现指针的作用,那么我们为什么要特地写一个类去实现指针呢,因为在我们开发过程中,我们希望指针可以有更多功能,而不仅仅是指向一个地址,那么我们就可以通过一个类去实现指针的功能的前提下,给它加上更多的功能,来满足我们的需求,比如说我们每使用一次指针,都要去释放掉这个指针,如果忘记释放,就会造成内存泄露,但我们无法使指针自己释放,但是如果是指针类,我们就可以在析构函数中加上delete语句,那么以后使用这个指针,就再也不用管它的释放问题了,C++特性中的智能指针其实就是这种原理,仿函数也是类似道理。


_ pointer-like classes

简单用代码实现一个智能指针,其实就是老版本的shared_ptr指针,可以明白其中的运作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#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 

首先看看智能指针为我们解决的问题,其实跟上面所说的基本一样。

当我们使用普通指针时:

1
2
3
4
5
6
7
8
9
10
11
void func(string & str)
{
...
string * s = new string(str);
if (error())
throw exception();
str = *s;
delete s;
...
return;
}

当程序出错,抛出异常,可以很明显看到发生了一个严重的问题,s指针未被释放,程序就中止了,导致内存泄露。

而我们如果使用智能指针auto_ptr

1
2
3
4
5
6
7
8
9
10
void func(string & str)
{
...
auto_ptr<string> s(new string(str));
if (error())
throw exception();
str = *s;
...
return;
}

程序抛出异常,auto_ptr会调用内部析构函数,释放掉指针,这样就避免了内存泄漏。

然后说一下为什么auto_ptr会被抛弃,auto_ptr的工作模式是拥有所有权。对于特定的对象,只能有一个智能指针可拥有,这样只有拥有对象的智能指针的析构函数会删除该对象。赋值操作会转让所有权。

可以看下面这一串代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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

仿函数也是用一个模板类去实现的,实现原理与仿指针差不多,由于目前不经常使用,了解不是很深,就先把代码语法问题解决了,以后再补充说明仿函数的使用环境与时机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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。