博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++_类入门5-智能指针模板类
阅读量:5945 次
发布时间:2019-06-19

本文共 7564 字,大约阅读时间需要 25 分钟。

智能指针是行为类似于指针的类对象,但这种对象还有其他功能。

本节介绍三个可帮助管理动态内存分配的智能指针模板auto_ptrunique_ptrshared_ptr)。

void remodel(std:string & str)

{

  std::string * ps = new std::string(str);

  ...

  str = ps;

  return;

}

这段代码有缺陷,每当调用时,该函数都分配堆中的内存,但从不回收,从而导致内存泄漏

但是有解决之道——在return语句前添加下面的语句,以释放分配的内存即可:

delete ps;

然而,但凡涉及“别忘了”的解决方法,很少是最佳的。因为有时候可能忘记了,有时候可能记住了。

即使确实没有忘记,但也可能有问题,如下:

void remodel(std::string & str)

{

  std::string * ps = new std::string(str);

  ...

  if (weird_thing())

    throw exception();

  str = *ps;

  delete ps;

  return;

}

当出现异常时,delete将不被执行,因此也将导致内存泄漏;

首先分析一下过程,当remodel()函数终止时,本地变量都将从栈内存中删除——因此指针ps占据的内存将被释放。

如果ps指向的内存也被释放,那该多好啊。如果ps有一个析构函数,该析构函数在ps过期时释放它所指向的内存。

因此问题就在于ps是一个常规的指针,不是有析构函数的类对象。如果它是对象,则可以在对象过期时,让它的析构函数删除指向的内存。

这正是智能指针模板背后的思想。

auto_ptr 是C++98提供的解决方案,但被C++11所摒弃;

C++提供了另外两种解决方案unique_ptr和shared_ptr;

=========================================

一、使用智能指针

三个智能指针模板(auto_ptr、unique_ptr和shared_ptr)都定义了类似指针的对象,可以将new获得的地址(直接或间接)赋给这种对象。

当智能指针过期时,其析构函数将使用delete来释放内存。

因此如果将new返回的地址赋给这些对象,将无需记住稍后释放这些内存:在智能指针过期时,这些内存将自动释放。

 

要创建智能指针对象,必须包含头文件memory,该文件模板定义。

然后使用通常的模板语法来实例化所需的类型指针。

例如:模板auto_ptr包含如下构造函数:

template <class X> class auto_ptr {

public:

  explicit auto_ptr(X*p = 0) throw();

...

};

 

auto_ptr<double> pd (new double);  //pd是auto_ptr to double,代替了double * pd

auto_ptr<string> ps (new string);     //ps是auto_ptr to string,代替了 string * ps

其中的new double是new返回的指针,指向新分配的内存块。它是构造函数auto_ptr<double>的参数;

 

因此,要转换remodel()函数,应按照下面3个步骤进行:

1、包含头文件memory;

2、将指向string的指针替换为指向string的智能指针对象;

3、删除delete语句;

下面是使用auto_ptr修改该函数的结果:

#include<memory>

void remodel(std::string & str)

{

  std::auto_ptr<std::string> ps (new std::string(str));

  ...

  if (weird_thing())

    throw exception();

  str = * ps;

  //delete ps; NO LONGER NEEDED

  return;

}

 

注意到智能指针位于std名称空间中,接下来的程序演示了如何使用全部三种指针。

1 //smrtptrs.cpp -- using three kinds of smart pointers 2 //requires support of C++11 shared_ptr and unique_ptr 3  4 #include 
5 #include
6 #include
7 8 class Report 9 {10 private:11 std::string str; 12 public:13 Report (const std::string s):str(s)14 {std::cout<<"Object created!\n";}15 ~Report() {std::cout<<"Object deleted!\n";}16 void comment() const {std::cout<
<<"\n";} 17 };18 19 int main()20 {21 {22 std::auto_ptr
ps (new Report("using auto_ptr"));23 ps -> comment(); //use -> to invoke a member function24 }25 {26 std::shared_ptr
ps (new Report("using shared_ptr"));27 ps -> comment();28 }29 {30 std::unique_ptr
ps (new Report("using unique_ptr"));31 ps->comment();32 }33 return 0;34 }

所有智能指针类都有一个explicit构造函数,该构造函数将指针作为参数。因此不需要自动将指针转换为智能指针对象://explicit直言的、不隐瞒的;//implicit不言明的、含蓄的

shared_ptr<double> pd;

double *p_reg = new double;

pd = p_reg;   // not allowed (implicit conversion)

pd = shared_ptr<double>(p_reg);  // allowed (explicit conversion)

shared_ptr<double> pshared = p_reg;  //not allowed (implicit conversion)

shared_ptr<double> pshared(p_reg);  //allowed (explicit conversion)

 

由于智能指针模板类的定义方式,所以其在很多方面都非常像常规指针。

例如,如果ps是一个智能指针对象,则可以对它执行解除引用操作(* ps)、用它来访问结构成员(ps->puffIndex)、将它赋给指向相同类型的常规指针。

还可以将智能指针对象赋给另一个同类型的智能指针对象,但将引起一个问题。在后续会讨论。

 

但在此之前,先说说对全部三种智能指针都应避免的一点

string vacation("I wandered lonely as a cloud.");

shared_ptr<string> pvac (&vacation);  //NO!

pvac过期时,程序将把delete运算符用于非堆内存,这是不对的;

 

=========================================

二、有关智能指针的注意事项

为何摒弃auto_ptr呢?首先看下下列语句:

auto_ptr<string> ps (new string("I reigned lonely as cloud."));

auto_ptr<string> vocation;

vocation = ps;

这两个指针将指向同一个string对象,这是不能接受的。因为程序将试图删除同一个对象两次——一次是ps过期时,另一次是vocation过期时。

要避免这种问题,有很多方法:

1、定义赋值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中一个对象是另一个对象的副本。

2、建立所有权(ownership)概念,对特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的析构函数会删除该对象。然后,让赋值操作转让所有权。这就是用于auto_ptr和unique_ptr的策略,但unique_ptr的策略更严格。

3、创建智能更高的指针,跟踪引用特定对象的智能指针数。这称之为引用计数(reference counting)。例如,赋值时,计数将加1,指针过期时,计数将减1。仅当最后一个指针过期时,才调用delete。这是shared_ptr采用的策略。

 

接下来是一个不适合使用auto_ptr的例子:

1 //fowl.cpp  -- auto_ptr a poor choice 2 #include 
3 #include
4 #include
5 6 int main() 7 { 8 using namespace std; 9 auto_ptr
films[5] =10 {11 auto_ptr
(new string("Fowl Balls")),12 auto_ptr
(new string("Duck Walks")),13 auto_ptr
(new string("Chicken Runs")),14 auto_ptr
(new string("Goose Eggs")),15 auto_ptr
(new string("Turkey Errors"))16 };17 auto_ptr
pwin;18 pwin = films[2];19 20 cout << "The nominees for best avian basketball film are\n";21 for (int i = 0; i < 5; i++)22 cout<<*films[i]<

下面是程序的输出:

The nominees for best avian baseball film are

Fowl Balls

Duck Walks

Segmentation fault(core dumped)

 

消息core dumped 表明,错误地使用auto_ptr可能导致问题。问题在于,下面的语句将所有权从film[2]转让给pwin:

pwn = films[2];

当film[2]不再引用该字符串,在auto_ptr放弃对象的所有权后,但有可能会使用它来访问该对象。

当程序打印film[2]指向的字符串时,却发现这是一个空指针,这显然是讨厌的意外。

 

如果程序使用shared_ptr代替auto_ptr,则程序将正常运行。这次pwinhefilms[2]指向同一个对象,而引用计数从1增加到2。

在程序末尾,后声明的pwin将首先调用其析构函数,该析构函数将引用计数降低到1。

然后,shared_ptr数组的成员被释放,对film[2]调用析构函数,将引用计数降低到0,并释放以前分配的空间。

 

因此使用shared_ptr,可以保证程序正常运行;使用auto_ptr会导致程序在运行阶段崩溃。

如果使用unique_ptr,结果将如何呢?与auto_ptr一样,unique_ptr也采用所有权模型。但使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译器因下述代码行出现错误:

pwin = films[2];

=========================================

三、unique_ptr为何优于auto_ptr

auto_ptr<string> p1 (new string("auto"));   //#1

auto_ptr<string> p2;   //#2

p2 =p1;  //#3

在语句#3中,p2接管string对象的所有权后,p1的所有权将被剥夺。

这是件好事,可以防止p1和p2的析构函数试图删除同一个对象。

但是如果随后程序试图使用p1,这将是件坏事,因为p1不再指向有效数据。

 

下面来看使用unique_ptr的情况:

unique_ptr<string> p3 (new string (''auto'));   //#4

unique_ptr<string> p4;       //#5

p4 = p3;          //#6

编译器认为语句#6非法,避免了p3不再指向有效数据的问题。

因此,unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更安全)。

 

//接下来讨论的东西会比较微妙

但有时候,将一个智能指针赋给另一个并不会留下危险的悬挂指针。

假设有如下函数定义:

unique_ptr<string> demo (const char * s)

{

    unique_ptr<string> temp(new string(s));

    return temp;

}

并假设编写了如下代码:

unique_ptr<string> ps;

ps =demo("Uniquely special");

其中demo()返回一个临时unique_ptr,然后ps接管了原本返回归unique_ptr的对象,而返回的unique_ptr被销毁。

这样做没有问题,因为ps拥有了string对象的所有权。 

这里做的另一个好处就是,demo返回的临时unique_ptr被销毁。也就没有机会来使用它返回无效数据。

换句话说,没有理由禁止这种赋值,神奇的是,编译器确实允许这种赋值。

总之,程序试图将unique_ptr赋值给另一个时,如果unique_ptr是一个临时右值的话,编译器允许这样做。

如果源unique_ptr将存在一段时间,编译器将禁止这样做。

 

//证明unique_ptr比auto_ptr好用

using namespace std;

unique_ptr<string> pu1 (new string "Hi ho!");

unique_ptr<string> pu2;

pu2 = pu1;                                                          //#1 not allowed

unique_ptr<string> pu3;

pu3 = unique_ptr<string>(new string "Yo!");      //#2 allowed

语句1将留下悬挂的unique_ptr指针,这将导致危害。

语句2不会留下悬挂的unique_ptr指针,因为它调用unique_ptr的构造函数,该构造函数创建的临时对象在其所有权转让给pu3后,会被销毁。

这种随情况而异的行为表明,unique_ptr优于auto_ptr。

 

//下面这段更加晦涩

如果我们确实想执行类型#1的语句那样的操作的话。

仅当以非智能的方式使用遗弃的智能指针,这种赋值才不安全。

要安全地重用这种指针,可给它赋新值。

C++有一个标准库函数std::move(),让您能够将一个unique_ptr赋给另一个。

 

using namespace std;

unique_ptr<string> ps1, ps2;

ps1 = demo("Uniquely special");

ps2 = move(ps1);             //允许赋值

ps1 = demo(" and more");

cout<< *ps2 << *ps1 <<endl;

这段代码中用到了移动构造函数和右值引用。

 

相对于auto_ptr,unique_ptr还有另一个优点。它有一个可用于数组的变体。别忘了,必须将delete和new配对,将delete[]和new[]配对。

而模板auto_ptr使用的是delete,而不是delete [],因此只能与new一起使用,而不能与new []一起使用。

但是unique_ptr有使用new[]和delete[]的版本。

=========================================

四、选择智能指针

应该如何选用只能指针?

如果程序要使用多个指向同一个对象的指针,应选择share_ptr。

这样的情况包括:有一个数组,并使用一些辅助指针来标识特定的元素,如最大的元素和最小的元素;

两个对象都包含指向第三个对象的指针;STL容器包含指针。

 

很多STL算法都支持赋值和复制操作。这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出警告)和auto_ptr(行为不确定)。

如果您的编译器没有支持shared_ptr,可使用Boost库提供的shared_ptr。

 

如果程序不需要多个指向同一个对象的指针,则可以使用unique_ptr。

如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。

这样所有权将转让给接受返回值的unique_ptr,而该指针将负责调用delete。

如果编译器没有提供unique_ptr,可考虑使用BOOST库提供的scoped_ptr,它与unique_ptr类似。

转载于:https://www.cnblogs.com/grooovvve/p/10467790.html

你可能感兴趣的文章
又转出61.8万个ETH,EOS不疯狂不成魔
查看>>
程序员面试IT公司的33个小贴士
查看>>
多款C系列手机亮相三星中国论坛,更加注重中国用户体验
查看>>
云南中医学院更名为云南中医药大学
查看>>
人社部:突出就业优先政策主线 全力确保就业局势稳定
查看>>
关键时刻还是要看阿里,达摩院发布自主研发AI芯片
查看>>
「百年育才」计划启动港股IPO,新高考改革下的“志愿填报辅导”市场迎来窗口期?...
查看>>
浅谈高性能数据库集群——读写分离
查看>>
HenCoder Android 开发进阶:自定义 View 1-4 Canvas 对绘制的辅助
查看>>
angular ui-router:简单的单页面嵌套路由的实现过程
查看>>
Poi导出产生OOM解决方案
查看>>
YYImage源码剖析与学习
查看>>
闭包和一部电影的关系
查看>>
小程序【二】
查看>>
使用Intellij创建springboot项目Spring Initializr Error 403
查看>>
0617 - 只做核心业务
查看>>
使用MVVM尝试开发Github客户端及对编程的一些思考
查看>>
算法-基础(一)数组基本操作 和 静态方法(后面编写算法的时候会用到)
查看>>
浏览器安全之同源策略
查看>>
把vue-cli build的结果放到服务器上
查看>>