C++智能指针与类模板

智能指针是行为相似于指针的类对象,全部的智能指针都会重载 -> 和 * 操做符。智能指针还有许多其余功能,比较有用的是自动销毁。这主要是利用栈对象的有限做用域以及临时对象(有限做用域实现)析构函数释放内存。固然,智能指针还不止这些,还包括复制时能够修改源对象等。智能指针根据需求不一样,设计也不一样(写时复制,赋值即释放对象拥有权限、引用计数等,控制权转移等)。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期控制,可以确保自动正确的销毁动态分配的对象,防止内存泄露。 智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次建立类的新对象时,初始化指针并将引用计数置为1;当对象做为另外一对象的副本而建立时,拷贝构造函数拷贝指针并增长与之相应的引用计数;对一个对象进行赋值时,赋值操做符减小左操做数所指对象的引用计数(若是引用计数为减至0,则删除对象),并增长右操做数所指对象的引用计数;调用析构函数时,构造函数减小引用计数(若是引用计数减至0,则删除基础对象)。 本文主要介绍3个可帮助管理动态内存分配的智能指针模板(auto_ptr、unique_ptr和shared_ptr)。html

目录ios

一、智能指针设计思想算法

二、智能指针简单介绍数组

三、为什么摒弃auto_ptr安全

四、unique_ptr为什么优于auto_ptr函数

五、如何选择智能指针ui

正文spa

一、智能指针设计思想.net

先来看一下须要哪些功能以及这些功能是如何实现的。请看下面的函数:设计

?

1

2

3

4

5

6

7

void remodel(std::string & str)

{

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

    ...

    str = ps;

    return;

}

 每当调用时,该函数都分配堆中的内存,但从不收回,从而致使内存泄漏。此时只要不忘记在return前添加下面语句释放内存便可:

?

1

delete ps ;

 然而,请你们再看一个例子:

?

1

2

3

4

5

6

7

8

9

10

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、unique_ptr和shared_ptr)背后的思想

按如下3个步骤用智能指针模板auto_ptr进行转换remodel()函数:

  • 包含头义件memory(智能指针所在的头文件);
  • 将指向string的指针替换为指向string的智能指针对象;
  • 删除delete语句。

?

1

2

3

4

5

6

7

8

9

10

11

12

#include <memory>

using namespace std;

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;

}

 二、智能指针简单介绍

在介绍以前先看一个简单程序,再对智能指针做一个简单的介绍。该程序演示了如何使用3种智能指针。

?

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

37

//smrtptrs.cpp --using three kinds of smart pointers

//requires support of C++11 shared_ptr and unique_ptr

#include <iostream>

#include <string>

#include <memory>

using namespace std;

 

class Report{

private:

    string str;

public:

    Report(const string s):str(s){

        cout << "Object created !" << endl;

    }

    ~Report(){

        cout << "Object deleted !" << endl;

    }

    void comment() const{

        cout << str << endl;

    }

};

 

int main(){

    {

        auto_ptr<Report> ps(new Report("using auto_ptr"));

        ps->comment();

    }

    {

        unique_ptr<Report> ps(new Report("using unique_ptr"));

        ps->comment();

    }

    {

        shared_ptr<Report> ps(new Report("using shared_ptr"));

        ps->comment();

    }

    return 0;

}

执行结果:

?

1

2

3

4

5

6

7

8

9

Object created !

using auto_ptr

Object deleted !

Object created !

using unique_ptr

Object deleted !

Object created !

using shared_ptr

Object deleted !

如今来分析一下这三个智能指针模板(auto_ptr、unique_ptr和shared_ptr)。

咱们能够将new得到(直接或者间接)的地址赋给这种对象,当智能指针过时时,其析构函数将使用delete来释放内存。

要建立智能指针对象,必须包含文件memory,该文件含有智能指针模板的定义,而后使用一般的模板语法来实例化所需类型的指针。全部的智能指针类都有一个explicit构造函数,例如,其中智能指针auto_ptr包含以下构造函数:

?

1

2

3

4

5

templet<class T>class auto_ptr {

public:

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

        ...

};

所以,请求X类型的auto_ptr将得到一个指向X类型的auto_ptr:

?

1

2

3

4

auto_ptr<double> pd(new double);// pd  an auto_ptr to double

// use in place of double *pd

auto_ptr<string> pd(new string);// pd  an auto_ptr to string

// use in place of double *string

new double 是new 返回的指针,指向新分配的内存块,它是auto_ptr<double>的参数,即对应于原型中形参p的实参。一样,new string也是构造函数的实参。另外两种智能指针使用一样的语法:

?

1

2

unique_ptr<double> pd(new double);

shared_ptr<double> pd(new double);

可是要注意的是,三种智能指针都应该避免一点:

?

1

2

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

shared_ptr<string> pvac(&vacation);   // No

pvac过时时,程序将把delete运算符用于非堆内存,这是错误的。

三、为什么摒弃auto_ptr

先来看下面的赋值语句:

?

1

2

3

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

auto_ptr<string>  vocation;

vocaticn = ps;

上述赋值语句将完成什么工做呢?若是ps和vocation是常规指针,则两个指针将指向同一个string对象。这是不能接受的,由于程序将试图删除同一个对象两次——一次是ps过时时,另外一次是vocation过时时。要避免这种问题,方法有多种:

  • 定义陚值运算符,使之执行深复制。这样两个指针将指向不一样的对象,其中的一个对象是另外一个对象的副本,缺点是浪费空间,因此智能指针都未采用此方案。

  • 创建全部权(ownership)概念。对于特定的对象,只能有一个智能指针可拥有,这样只有拥有对象的智能指针的构造函数会删除该对象。而后让赋值操做转让全部权。这就是用于auto_ptr和uniqiie_ptr 的策略,但unique_ptr的策略更严格。

  • 建立智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加1,而指针过时时,计数将减1,。当减为0时才调用delete。这是shared_ptr采用的策略。

固然,一样的策略也适用于复制构造函数。

每种方法都有其用途,但为什么说要摒弃auto_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

#include <iostream>

#include <string>

#include <memory>

using namespace std;

 

int main() {

  auto_ptr<string> films[5] =

 {

  auto_ptr<string> (new string("Fowl Balls")),

  auto_ptr<string> (new string("Duck Walks")),

  auto_ptr<string> (new string("Chicken Runs")),

  auto_ptr<string> (new string("Turkey Errors")),

  auto_ptr<string> (new string("Goose Eggs"))

 };

 auto_ptr<string> pwin;

 pwin = films[2]; // films[2] loses ownership. 将全部权从films[2]转让给pwin,此时films[2]再也不引用该字符串从而变成空指针

 

 cout << "The nominees for best avian baseballl film are\n";

 for(int i = 0; i < 5; ++i)

  cout << *films[i] << endl;

 cout << "The winner is " << *pwin << endl;

 cin.get();

 

 return 0;

}

该程序运行结果是崩溃了,可是,若是把auto_ptr<string> pwin;改为shared_ptr<string> pwin;编译后执行结果却正常了。若是把auto_ptr<string> pwin;改为unique_ptr<string> pwin;编译语句pwin = films[2];时不能经过。

缘由以下:

  • 使用shared_ptr时运行正常,由于shared_ptr采用引用计数,pwin和films[2]都指向同一块内存,在释放空间时由于事先要判断引用计数值的大小所以不会出现屡次删除一个对象的错误。

  • 使用unique_ptr时编译出错,与auto_ptr同样,unique_ptr也采用全部权模型,但在使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译器因下述代码行出现错误。

四、unique_ptr为什么优于auto_ptr

可能你们认为前面的例子已经说明了unique_ptr为什么优于auto_ptr,也就是安全问题,下面再叙述的清晰一点。请看下面的语句:

?

1

2

3

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的状况:

?

1

2

3

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

unique_ptr<string> p4;                       //#5

p4 = p3;                                      //#6

编译器认为语句#6非法,避免了p3再也不指向有效数据的问题。所以,unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更安全)。

有时候,会将一个智能指针赋给另外一个并不会留下危险的悬挂指针。假设有以下函数定义:

?

1

2

3

4

5

unique_ptr<string> demo(const char * s)

{

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

    return temp;

}

并假设编写了以下代码:

?

1

unique_ptr<string> ps;<br>ps = demo('Uniquely special");

demo() 返回一个临时unique_ptr,而后ps接管了本来归返回的unique_ptr全部的对象,而返回时临时的 unique_ptr 被销毁,也就是说没有机会使用 unique_ptr 来访问无效的数据,换句话来讲,这种赋值是不会出现任何问题的,即没有理由禁止这种赋值。实际上,编译器确实容许这种赋值。

总之,当程序试图将一个 unique_ptr 赋值给另外一个时,若是源 unique_ptr 是个临时右值,编译器容许这么作;若是源 unique_ptr 将存在一段时间,编译器将禁止这么作,好比:

?

1

2

3

4

unique_ptr<string> pu1(new string ("hello world"));

unique_ptr<string> pu2;_x000D_pu2 = pu1;          // #1 not allowed

unique_ptr<string> pu3;

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

其中#1留下悬挂的unique_ptr(pu1),这可能致使危害。而#2不会留下悬挂的unique_ptr,由于它调用 unique_ptr 的构造函数,该构造函数建立的临时对象在其全部权让给 pu3 后就会被销毁。这种随状况而已的行为代表,unique_ptr 优于容许两种赋值的auto_ptr 。

固然,您可能确实想执行相似于#1的操做,仅当以非智能的方式使用摒弃的智能指针时(如解除引用时),这种赋值才不安全。要安全的重用这种指针,可给它赋新值。C++ 有一个标准库函数std::move(),让你可以将一个unique_ptr赋给另外一个。原来的指针仍转让全部权变成空指针,能够对其从新赋值。下面是一个使用前述demo()函数的例子,该函数返回一个 unique_ptr<string> 对象:

?

1

2

3

4

5

unique_ptr<string> ps1, ps2;

ps1 = demo("hello");

ps2 = move(ps1);

ps1 = demo("alexia");

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

相比于auto_ptr,unique_ptr还有另外一个优势,它有一个可用于数组的变体,这里就不做细节讨论了。

五、如何选择智能指针

在现实中咱们该如何选择智能指针呢?下面给出几个选择方法:

  • 若是程序要使用多个指向同一个对象的指针,应选择shared_ptr。
  1. 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
  2. 两个对象包含都指向第三个对象的指针;
  3. STL容器包含指针。不少STL算法都支持复制和赋值操做,这些操做可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不肯定)。若是你的编译器没有提供shared_ptr,可以使用Boost库提供的shared_ptr。
  • 若是程序不须要多个指向同一个对象的指针,则可以使用unique_ptr。
  • 若是函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,全部权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另外一个算法(如sort())。例如,可在程序中使用相似于下面的代码段:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

unique_ptr<int> make_int(int n)

{

    return unique_ptr<int>(new int(n));

}

void show(unique_ptr<int> &p1)

{

    cout << *a << ' ';

}

int main()

{

    ...

    vector<unique_ptr<int> > vp(size);

    for(int i = 0; i < vp.size(); i++)

        vp[i] = make_int(rand() % 1000);              // copy temporary unique_ptr

    vp.push_back(make_int(rand() % 1000));     // ok because arg is temporary

    for_each(vp.begin(), vp.end(), show);           // use for_each()

    ...

}

其中push_back调用没有问题,由于它返回一个临时unique_ptr,该unique_ptr被赋给vp中的一个unique_ptr。另外,如 果按值而不是按引用给show()传递对象,for_each()将非法,由于这将致使使用一个来自vp的非临时unique_ptr初始化pi,而这是 不容许的。前面说过,编译器将发现错误使用unique_ptr的企图。

在unique_ptr为右值时,可将其赋给shared_ptr,这与将一个unique_ptr赋给一个须要知足的条件相同。与前面同样,在下面的代码中,make_int()的返回类型为unique_ptr<int>:

?

1

2

3

unique_ptr<int> pup(make_int(rand() % 1000));   // ok

shared_ptr<int> spp(pup);                       // not allowed, pup as lvalue

hared_ptr<int> spr(make_int(rand() % 1000));   // ok

模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。shared_ptr将接管原来归unique_ptr全部的对象。

在知足unique_ptr要求的条件时,也可以使用auto_ptr,但unique_ptr是更好的选择。若是你的编译器没有unique_ptr,可考虑使用Boost库提供的scoped_ptr,它与unique_ptr相似。

 

https://blog.csdn.net/crusierliu/article/details/82432265

相关文章
相关标签/搜索