前言
无论是在C
还是C++
中,指针都是在使用的时候需要非常谨慎的一个点,而在C++
中,我们引入一个智能指针的概念,以此来规避在使用指针时可能出现的问题。
智能指针的引入
我们以之前的一个程序为例子,也就是Person
类,如下是Person
类的代码:
class Person {
public:
Person()
{
cout <<"Pserson()"< }
~Person()
{
cout << "~Person()"< }
void printInfo(void)
{
cout<<"just a test function"< }
};
基于此,我们来编写一个测试函数:
void test_func(void)
{
Person *p = new Person();
p->printInfo();
}
可以看到在测试函数里,我们定义了一个指针变量,但是,这里需要注意的是,这个指针变量并没有delete
操作,紧接着,我们来编写main
函数,代码如下所示:
int main(int argc, char **argv)
{
int i;
for (i = 0; i < 2; i++)
test_func();
return 0;
}
这样的程序存在一个什么隐患呢?如果在main
函数中的i
的最大值是是一个很大的数,那么程序就会调用很多次test_func
函数,但是由于test_func
函数里没有delete
操作,那么这个时候由new
获得的内存就会一直不能得到释放,最终导致程序崩溃。
我们将test_func
函数进行一些更改,更改如下所示:
void test_func(void)
{
Person per;
per.printInfo();
}
main
函数不变,这个时候如下i
的最大值是一个很大的数,那么会导致程序崩溃么,答案是否定的,因为在这里,在test_func
函数里定义的是一个局部变量,局部变量是存放在栈里的,也就是说每当test_func
执行完局部变量就会出栈,其所占用的空间自然也就释放了。
智能指针
所以,这给我们一个启发,如果将指针和局部变量相联系起来,是不是就能解决使用指针所带来的隐患呢?我们来看下面这样一个代码(Person类的代码不变)
:
class sp
{
private:
Person *p;
public:
sp() : p(0) {}
sp(Person *other)
{
cout << "sp(Person *other)" << endl;
p = other;
}
~sp()
{
cout << "~sp()" << endl;
if (p)
delete p;
}
Person *operator->() /* -> 被重载,是为了使得 sp 实例化的对象能够访问到 person 类的成员函数*/
{
return p;
}
};
基于此,我们来编写test_func
函数:
void test_func(void)
{
sp s = new Person();
s->printInfo();
}
同样的main
函数不变,在这种情况下,test_func
的执行就不会导致程序崩溃,因为此时实际上是定义了一个局部变量,在函数执行完毕之后,局部变量也就会自动地释放掉。
我们继续完善代码,我们在sp
类中增加一个拷贝构造函数,增加的代码如下所示:
class sp
{
private:
Person *p;
public:
/*省略前面已有的代码*/
sp(sp &other)
{
cout << "sp(sp &other)" << endl;
p = other.p
}
};
在增加了拷贝构造函数的基础上,我们编写main
函数:
int main(int argc, char** argv)
{
sp other = new Person();
return 0;
}
我们编译代码,编译结果如下所示:
image-20210228172543467
上述错误的提示是说,不能将非常亮的引用与临时变量绑定,到底是什么意思呢,我们来看下面的分析,我们看主函数的这条语句:
sp other = new person();
这条语句实际上可以等同于如下这几条语句:
Person *p = new Person();
sp tmp(p); ==> sp(Person *p) /*tmp 表示的是临时变量*/
sp other(tmp); ==> sp(sp &other2)
那为什么会报错呢?这是因为第三条语句,我们将第三条语句进行以下剖析,第三条语句实际上是相当于下面这条语句:
sp &other2 = tmp;
那这条语句是为什么会出错呢,这是因为tmp
当前是一个临时变量,而临时变量是不能够赋值给非常量引用的。
临时变量没有名字,自然不能够赋值给非常量引用
而解决方法,也很简单,那就改成常量引用就好了,因此,我们将拷贝构造函数改为如下的形式:
class sp
{
private:
Person *p;
public:
/*省略前面已有的代码*/
sp(const sp &other)
{
cout << "sp(sp &other)" << endl;
p = other.p
}
};
这样一来就解决这个问题了。
我们继续更改代码,将test_func
代码改为如下的形式:
void test_func(sp &other)
{
sp s = other;
s->printInfo();
}
然后,基于此,我们在主函数里测试test_func
函数,测试代码如下所示:
int main(int argc, char **argv)
{
int i;
sp other = new Person();
for (i = 0; i < 2; i++)
test_func(other);
return 0;
}
编译,运行代码,结果如下所示:
image-20210228201922544
上述运行的结果提示是当前被释放了两次,这是为什么呢?我们来仔细分析一下,下面是程序执行的一个流程图:
image-20210228203637110
因此,这也就解释了上述出错的原因,那么可以采取什么方法来解决这个错误呢?原理也是简单的,只要不让它销毁两次就行,那我们采取的方法是,定义一个变量,这个变量能够记录指向Person
对象的个数,只有当前指向这个Person
对象的个数为0
的时候,才执行销毁操作,否则就不执行销毁操作。
下面我们来编写代码,首先是Person
类的代码:
class Person
{
private:
int count;
public:
void incStrong { count++; }
void decStrong { count--; }
void getStrongCount { return count; } /* 因为当前 count 属于是私有数据成员,自然编写这些访问接口是很有必要了 */
Person() : count(0)
{
cout << "Person()" << endl;
}
~Person()
{
cout << "~Person()" << endl;
}
void printInfo(void)
{
cout << "just a test function" << endl;
}
};
上述代码中,我们在Person
类中定义了私有数据成员,并且定义了其访问的接口,同时,我们在Person
的构造函数中,初始化了count
变量。
紧接着,我们来编写sp
类的代码,注意:我们在讲述原理的时候,提到了定义一个能够记录指向Person
类次数的变量,那么在接下来的代码中,只要涉及指向Person
类的操作的时候,就需要将count
加一,下面是sp
类的代码:
class sp
{
private:
Person *p;
public:
sp() : p(0) {}
sp(Person *other)
{
cout << "sp(Person *other)" << endl;
p = other;
p->incStrong();
}
sp(const sp &other)
{
cout << "sp(const sp &other)" << endl;
p = other.p;
p->incStrong();
}
~sp()
{
cout << "~sp()" << endl;
if (p)
{
p->decStrong();
if (p->getStrongCount() == 0)
{
delete p;
p = NULL;
}
}
}
Person* operator->()
{
return p;
}
};
为了更好地观察代码的运行,我们增加一些打印信息用于观察,首先是test_func
里的,增加的代码如下所示:
void test_func(sp &other)
{
sp s = other;
cout<<"In test_func: "<getStrongCount()<
s->printInfo();
}
然后,我们继续来编写main
函数里面的代码:
int main(int argc, char **argv)
{
int i;
sp other = new Person();
cout<<"Before call test_func: "<getStrongCount()<
for (i = 0; i < 2; i++)
{
test_func(other);
cout<<"After call test_func: "<getStrongCount()< }
return 0;
}
编译,执行,下面是代码执行的结果:
image-20210228210842670
对照着代码,我们可以看到Person
对象被指向的次数,而且在更改之后的基础上运行,代码就没有出现错误了。
现在来小结一下,在使用了智能指针之后,在遇到需要定义指针型变量的时候,我们也更加倾向于使用下面的方式:
少用Person*
,而是用sp
来替代Person*
对于 Person*
来说,有两种操作:per->XXX
或者是(*per).XXX
那么对于sp
来说,也应该有这两种操作:sp->XXX
或者是(*sp).XXX
为了实现(*sp).XXX
,那么我们还需要额外补充一点,就是关于*
运算符的重载,重载的代码如下:
class sp
{
private:
Person *p;
public:
/* 省略相关代码 */
Person& operator*()
{
return *p;
}
};
另外需要注意的一点就是上述中使用&
而不是直接返回值的原因是为了提高效率,因为如果是返回值的话就需要调用构造函数,而如果是返回引用的话就不需要。
改进
那么到目前为止,我们的代码还能不能再进行完善呢?我们来看Person
类的代码,关于count
相关的代码,实际上只要涉及到构造一个智能指针,那么就会用的到,而这个时候,可以把这部分代码单独分离出来,然后,Person
类可以从这个分离出来的类继承,这样就更加具有普适性,比如,我们如果想要构造一个其他的智能指针,所需要的类就可以从这个分离出来的类中继承。我们来看具体的代码:
class RefBase {
private:
int count;
public:
RefBase() : count(0) {}
void incStrong(){ count++; }
void decStrong(){ count--; }
int getStrongCount(){ return count;}
};
上述就是我们分离出来的类,然后Person
类从这个类中继承而来。
class Person : public RefBase{
public:
Person() {
cout <<"Pserson()"< }
~Person()
{
cout << "~Person()"< }
void printInfo(void)
{
cout<<"just a test function"< }
};
上述是我们对于Person
类的一个改进,我们还可以进一步进行改进,回顾sp
类,sp 类中所定义的私有成员是Person
类的实例化对象,那么如果我想要用sp
定义任何类型的对象呢,这个时候,就需要使用到模板的概念,下面是改进后的sp
类的模板函数的代码:
template
class sp
{
private:
T *p;
sp() : p(0) {}
sp(T *other)
{
cout<<"sp(T *other)"< p = other;
p->incStrong();
}
sp(const sp &other)
{
cout<<"sp(const sp &other)"< p = other.p;
p->incStrong();
}
~sp()
{
cout<<"~sp()"<
if (p)
{
p->decStrong();
if (p->getStrongCount() == 0)
{
delete p;
p = NULL;
}
}
}
T *operator->()
{
return p;
}
T& operator*()
{
return *p;
}
}
实际上也很简单,只是将之前的Person
换成了T
。更改了sp
类,那么也就自然需要更改test_func
函数了,更改之后的代码如下所示:
template
void test_func(sp &other)
{
sp s = other;
cout<<"In test_func: "<getStrongCount()<
s->printInfo();
}
基于上述的改进,我们来编写主函数,代码如下所示:
int main(int argc, char** argv)
{
int i;
sp other = new Person();
(*other).printInfo();
cout<<"Before call test_func: "<getStrongCount()<
for (i = 0; i < 2; i++)
{
test_func(other);
cout<<"After call test_func: "<getStrongCount()< }
return 0;
}
至此,就完成了关于智能指针的改进,当然,到目前为止,其还是存在问题的,所存在的问题,将在下一节进行叙述。
小结
本节的内容就到这里结束了,所涉及的代码可以通过百度云链接的方式获取到:
链接:/zixunimg/eefocusimg/pan.baidu.com/s/1LUL6HqekmwguqYO6V1ETqw
提取码:vu8p