Maison >développement back-end >Tutoriel C#.Net >Explication détaillée des pointeurs intelligents C

Explication détaillée des pointeurs intelligents C

黄舟
黄舟original
2017-02-06 13:41:491814parcourir

Explication détaillée des pointeurs intelligents C


1 Introduction

Le langage C n'ayant pas de mécanisme de recyclage automatique de la mémoire, les programmeurs doivent en utiliser de nouveaux. mémoire à chaque fois. Il n'est pas rare que les programmeurs oublient de supprimer, le processus est trop compliqué, et finalement il n'y a pas de suppression, et des exceptions provoquent la fermeture prématurée du programme sans exécuter la suppression.

L'utilisation de pointeurs intelligents peut atténuer efficacement ces problèmes. Cet article explique principalement l'utilisation des pointeurs intelligents. Y compris : std::auto_ptr, boost::scoped_ptr, boost::shared_ptr, boost::scoped_array, boost::shared_array, boost::weak_ptr, boost::intrusive_ptr. Vous vous demandez peut-être si autant de pointeurs intelligents sont vraiment nécessaires pour résoudre les problèmes de correspondance nouveaux et supprimés ? Après avoir lu cet article, je pense que vous aurez naturellement la réponse en tête.

Les sept types de pointeurs intelligents suivants (smart_ptr) seront expliqués dans l'ordre.

2. Utilisation spécifique


1.Résumé

Pour le compilateur, un pointeur intelligent est en fait un objet de pile, pas un type Pointeur. , lorsque la vie de l'objet pile est sur le point de se terminer, le pointeur intelligent libère la mémoire tas qu'il gère via le destructeur. Tous les pointeurs intelligents sont surchargés avec l'opérateur "operator->", qui renvoie directement une référence à l'objet et est utilisé pour faire fonctionner l'objet. La méthode originale d'accès aux pointeurs intelligents utilise l'opérateur "."

Pour accéder au pointeur brut contenu dans le pointeur intelligent, vous pouvez utiliser la fonction get(). Puisqu'un pointeur intelligent est un objet, if (my_smart_object) est toujours vrai. Pour déterminer si le pointeur brut du pointeur intelligent est nul, vous devez déterminer comme ceci : if (my_smart_object.get()).

Le pointeur intelligent contient la méthode reset(). Si aucun paramètre n'est passé (ou si NULL est passé), le pointeur intelligent libérera la mémoire actuellement gérée. Si un objet est transmis, le pointeur intelligent libérera l'objet actuel pour gérer l'objet nouvellement transmis.


Nous écrivons une classe de test pour faciliter l'analyse :

class Simple {
public:
  Simple(int param = 0) {
    number = param;
    std::cout << "Simple: " << number << std::endl;
  }
  ~Simple() {
    std::cout << "~Simple: " << number << std::endl;
  }
  void PrintSomething() {
    std::cout << "PrintSomething: " << info_extend.c_str() << std::endl;
  }
  std::string info_extend;
  int number;
};


2. >


std::auto_ptr appartient à STL Bien sûr, il peut être utilisé dans l'espace de noms std en incluant le fichier d'en-tête #include. std::auto_ptr peut facilement gérer un seul objet de mémoire tas.


On commence par l'analyse du code :

void TestAutoPtr() {
std::auto_ptr<Simple> my_memory(new Simple(1));   // 创建对象,输出:Simple:1
if (my_memory.get()) {                            // 判断智能指针是否为空
my_memory->PrintSomething();                    // 使用 operator-> 调用智能指针对象中的函数
my_memory.get()->info_extend = "Addition";      // 使用 get() 返回裸指针,然后给内部对象赋值
my_memory->PrintSomething();                    // 再次打印,表明上述赋值成功
(*my_memory).info_extend += " other";           // 使用 operator* 返回智能指针内部对象,然后用“.”调用智能指针对象中的函数
my_memory->PrintSomething();                    // 再次打印,表明上述赋值成功
  }
}                                               // my_memory 栈对象即将结束生命期,析构堆对象 Simple(1)


Le résultat de l'exécution est :

Simple: 1
PrintSomething:
PrintSomething: Addition
PrintSomething: Addition other
~Simple: 1


Ce qui précède est le code pour une utilisation normale de std::auto_ptr. Tout semble bien. Dans tous les cas, nous n'avons pas besoin d'utiliser explicitement cette foutue suppression.


En fait, les bons moments n'ont pas duré longtemps. Regardons un autre exemple comme suit :

void TestAutoPtr2() {
  std::auto_ptr<Simple> my_memory(new Simple(1));
  if (my_memory.get()) {
    std::auto_ptr<Simple> my_memory2;   // 创建一个新的 my_memory2 对象
    my_memory2 = my_memory;             // 复制旧的 my_memory 给 my_memory2
    my_memory2->PrintSomething();       // 输出信息,复制成功
    my_memory->PrintSomething();        // 崩溃
}
}


Le code final est comme ci-dessus Provoque un crash. Le code ci-dessus est absolument conforme aux idées de programmation C, mais il plante en fait. Après avoir suivi le code source de std::auto_ptr, nous voyons que le coupable est "my_memory2". = my_memory". Dans cette ligne de code, my_memory2 prend complètement le contrôle de my_memory. Propriété de la gestion de la mémoire, provoquant le blocage de my_memory, provoquant un crash lors de la dernière utilisation.


Ainsi, lorsque vous utilisez std::auto_ptr, vous ne devez pas utiliser l'opérateur "operator=". En tant que bibliothèque, les utilisateurs ne sont pas autorisés à l'utiliser et elle n'est pas explicitement rejetée [1], ce qui est quelque peu inattendu.


Après avoir lu le premier exemple de std::auto_ptr qui n'a pas duré longtemps, regardons-en un autre :

void TestAutoPtr3() {
  std::auto_ptr<Simple> my_memory(new Simple(1));
  if (my_memory.get()) {
    my_memory.release();
  }
}


Le résultat de l'exécution est :

Avez-vous vu quelque chose d'anormal ? L'objet que nous avons créé n'a pas été détruit et "~Simple: 1" n'a pas été affiché, ce qui a entraîné une fuite de mémoire. Lorsque nous ne voulons pas que my_memory continue à survivre, nous appelons la fonction release() pour libérer la mémoire, ce qui entraîne une fuite de mémoire (dans un système à mémoire limitée, si my_memory prend trop de mémoire, nous envisagerons de renvoyer immédiatement après la fin de son utilisation et au lieu d'attendre que my_memory termine sa vie, il sera restitué).
Simple: 1



Le code correct doit être :

void TestAutoPtr3() {
  std::auto_ptr<Simple> my_memory(new Simple(1));
  if (my_memory.get()) {
    Simple* temp_memory = my_memory.release();
    delete temp_memory;
  }
}


ou

void TestAutoPtr3() {
  std::auto_ptr<Simple> my_memory(new Simple(1));
  if (my_memory.get()) {
    my_memory.reset();  // 释放 my_memory 内部管理的内存
  }
}


Il s'avère que la fonction release() de std::auto_ptr abandonne simplement la propriété de la mémoire, ce qui n'est évidemment pas conforme aux idées de programmation C.

Résumé : std::auto_ptr peut être utilisé pour gérer la mémoire d'un seul objet. Cependant, veuillez faire attention aux points suivants :

(1) Essayez de ne pas utiliser "operator=. ". S’il est utilisé, n’utilisez plus l’objet précédent.

(2) N'oubliez pas que la fonction release() ne libère pas l'objet, mais rend seulement la propriété.

(3) std::auto_ptr est préférable de ne pas être passé en paramètre (les lecteurs peuvent écrire leur propre code pour déterminer pourquoi).

(4) En raison du problème "operator=" de std::auto_ptr, les objets gérés par celui-ci ne peuvent pas être placés dans des conteneurs tels que std::vector.

(5) ...


Il y a tellement de restrictions sur l'utilisation d'un std::auto_ptr Il ne peut pas être utilisé pour gérer des matrices de mémoire tas. Cela devrait être à quoi pensez-vous maintenant ? Je pense aussi qu'il y a pas mal de restrictions, un jour, si vous n'y faites pas attention, cela posera des problèmes.


Parce que std::auto_ptr a causé de nombreux problèmes, certaines conceptions ne sont pas très cohérentes avec les idées de programmation C, de sorte que les pointeurs intelligents Boost suivants ont été déclenchés. résoudre la question des problèmes ci-dessus.


Continuons à regarder vers le bas.


3. boost::scoped_ptr

boost::scoped_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件 #includee23b372897c2668fffd6001e3ccdaabb 便可以使用。boost::scoped_ptr 跟 std::auto_ptr 一样,可以方便的管理单个堆内存对象,特别的是,boost::scoped_ptr 独享所有权,避免了 std::auto_ptr 恼人的几个问题。


我们还是从代码开始分析:

void TestScopedPtr() {
  boost::scoped_ptr<Simple> my_memory(new Simple(1));
  if (my_memory.get()) {
    my_memory->PrintSomething();
    my_memory.get()->info_extend = "Addition";
    my_memory->PrintSomething();
    (*my_memory).info_extend += " other";
    my_memory->PrintSomething();
  
    my_memory.release();           // 编译 error: scoped_ptr 没有 release 函数
    std::auto_ptr<Simple> my_memory2;
    my_memory2 = my_memory;        // 编译 error: scoped_ptr 没有重载 operator=,不会导致所有权转移
  }
}


首先,我们可以看到,boost::scoped_ptr 也可以像 auto_ptr 一样正常使用。但其没有 release() 函数,不会导致先前的内存泄露问题。其次,由于 boost::scoped_ptr 是独享所有权的,所以明确拒绝用户写“my_memory2 = my_memory”之类的语句,可以缓解 std::auto_ptr 几个恼人的问题。


由于 boost::scoped_ptr 独享所有权,当我们真真需要复制智能指针时,需求便满足不了了,如此我们再引入一个智能指针,专门用于处理复制,参数传递的情况,这便是如下的 boost::shared_ptr。


4、boost::shared_ptr


boost::shared_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件 #includee23b372897c2668fffd6001e3ccdaabb 便可以使用。在上面我们看到 boost::scoped_ptr 独享所有权,不允许赋值、拷贝,boost::shared_ptr 是专门用于共享所有权的,由于要共享所有权,其在内部使用了引用计数。boost::shared_ptr 也是用于管理单个堆内存对象的。


我们还是从代码开始分析:

void TestSharedPtr(boost::shared_ptr<Simple> memory) {  // 注意:无需使用 reference (或 const reference)
  memory->PrintSomething();
  std::cout << "TestSharedPtr UseCount: " << memory.use_count() << std::endl;
}
void TestSharedPtr2() {
  boost::shared_ptr<Simple> my_memory(new Simple(1));
  if (my_memory.get()) {
    my_memory->PrintSomething();
    my_memory.get()->info_extend = "Addition";
    my_memory->PrintSomething();
    (*my_memory).info_extend += " other";
    my_memory->PrintSomething();
  }
  std::cout << "TestSharedPtr2 UseCount: " << my_memory.use_count() << std::endl;
  TestSharedPtr(my_memory);
  std::cout << "TestSharedPtr2 UseCount: " << my_memory.use_count() << std::endl;
  //my_memory.release();// 编译 error: 同样,shared_ptr 也没有 release 函数
}


执行结果为:

Simple: 1
PrintSomething:
PrintSomething: Addition
PrintSomething: Addition other
TestSharedPtr2 UseCount: 1
PrintSomething: Addition other
TestSharedPtr UseCount: 2
TestSharedPtr2 UseCount: 1
~Simple: 1


boost::shared_ptr 也可以很方便的使用。并且没有 release() 函数。关键的一点,boost::shared_ptr 内部维护了一个引用计数,由此可以支持复制、参数传递等。boost::shared_ptr 提供了一个函数 use_count() ,此函数返回 boost::shared_ptr 内部的引用计数。查看执行结果,我们可以看到在 TestSharedPtr2 函数中,引用计数为 1,传递参数后(此处进行了一次复制),在函数TestSharedPtr 内部,引用计数为2,在 TestSharedPtr 返回后,引用计数又降低为 1。当我们需要使用一个共享对象的时候,boost::shared_ptr 是再好不过的了。


在此,我们已经看完单个对象的智能指针管理,关于智能指针管理数组,我们接下来讲到。


5、boost::scoped_array


boost::scoped_array 属于 boost 库,定义在 namespace boost 中,包含头文件 #includee23b372897c2668fffd6001e3ccdaabb 便可以使用。


boost::scoped_array 便是用于管理动态数组的。跟 boost::scoped_ptr 一样,也是独享所有权的。


我们还是从代码开始分析:

void TestScopedArray() {
      boost::scoped_array<Simple> my_memory(new Simple[2]); // 使用内存数组来初始化
      if (my_memory.get()) {
        my_memory[0].PrintSomething();
        my_memory.get()[0].info_extend = "Addition";
        my_memory[0].PrintSomething();
        (*my_memory)[0].info_extend += " other";            // 编译 error,scoped_ptr 没有重载 operator*
        my_memory[0].release();                             // 同上,没有 release 函数
        boost::scoped_array<Simple> my_memory2;
        my_memory2 = my_memory;                             // 编译 error,同上,没有重载 operator=
      }
}


boost::scoped_array 的使用跟 boost::scoped_ptr 差不多,不支持复制,并且初始化的时候需要使用动态数组。另外,boost::scoped_array 没有重载“operator*”,其实这并无大碍,一般情况下,我们使用 get() 函数更明确些。


下面肯定应该讲 boost::shared_array 了,一个用引用计数解决复制、参数传递的智能指针类。


6、boost::shared_array


boost::shared_array 属于 boost 库,定义在 namespace boost 中,包含头文件 #includee23b372897c2668fffd6001e3ccdaabb 便可以使用。


由于 boost::scoped_array 独享所有权,显然在很多情况下(参数传递、对象赋值等)不满足需求,由此我们引入 boost::shared_array。跟 boost::shared_ptr 一样,内部使用了引用计数。


我们还是从代码开始分析:

void TestSharedArray(boost::shared_array<Simple> memory) {  // 注意:无需使用 reference (或 const reference)
  std::cout << "TestSharedArray UseCount: " << memory.use_count() << std::endl;
}
void TestSharedArray2() {
  boost::shared_array<Simple> my_memory(new Simple[2]);
  if (my_memory.get()) {
    my_memory[0].PrintSomething();
    my_memory.get()[0].info_extend = "Addition 00";
    my_memory[0].PrintSomething();
    my_memory[1].PrintSomething();
    my_memory.get()[1].info_extend = "Addition 11";
    my_memory[1].PrintSomething();
    //(*my_memory)[0].info_extend += " other";  // 编译 error,scoped_ptr 没有重载 operator*
  }
  std::cout << "TestSharedArray2 UseCount: " << my_memory.use_count() << std::endl;
  TestSharedArray(my_memory);
  std::cout << "TestSharedArray2 UseCount: " << my_memory.use_count() << std::endl;
}


执行结果为:

Simple: 0
Simple: 0
PrintSomething:
PrintSomething: Addition 00
PrintSomething:
PrintSomething: Addition 11
TestSharedArray2 UseCount: 1
TestSharedArray UseCount: 2
TestSharedArray2 UseCount: 1
~Simple: 0
~Simple: 0


跟 boost::shared_ptr 一样,使用了引用计数,可以复制,通过参数来传递。


至此,我们讲过的智能指针有 std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array。这几个智能指针已经基本够我们使用了,90% 的使用过标准智能指针的代码就这 5 种。可如下还有两种智能指针,它们肯定有用,但有什么用处呢,一起看看吧。


7、boost::weak_ptr


boost::weak_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件 #includee23b372897c2668fffd6001e3ccdaabb 便可以使用。


在讲 boost::weak_ptr 之前,让我们先回顾一下前面讲解的内容。似乎 boost::scoped_ptr、boost::shared_ptr 这两个智能指针就可以解决所有单个对象内存的管理了,这儿还多出一个 boost::weak_ptr,是否还有某些情况我们没纳入考虑呢?


回答:有。首先 boost::weak_ptr 是专门为 boost::shared_ptr 而准备的。有时候,我们只关心能否使用对象,并不关心内部的引用计数。boost::weak_ptr 是 boost::shared_ptr 的观察者(Observer)对象,观察者意味着 boost::weak_ptr 只对 boost::shared_ptr 进行引用,而不改变其引用计数,当被观察的 boost::shared_ptr 失效后,相应的 boost::weak_ptr 也相应失效。


我们还是从代码开始分析:

void TestWeakPtr() {
      boost::weak_ptr<Simple> my_memory_weak;
      boost::shared_ptr<Simple> my_memory(new Simple(1));
      std::cout << "TestWeakPtr boost::shared_ptr UseCount: " << my_memory.use_count() << std::endl;
      my_memory_weak = my_memory;
      std::cout << "TestWeakPtr boost::shared_ptr UseCount: " << my_memory.use_count() << std::endl;
}


执行结果为:

Simple: 1
TestWeakPtr boost::shared_ptr UseCount: 1
TestWeakPtr boost::shared_ptr UseCount: 1
~Simple: 1


我们看到,尽管被赋值了,内部的引用计数并没有什么变化,当然,读者也可以试试传递参数等其他情况。

现在要说的问题是,boost::weak_ptr 到底有什么作用呢?从上面那个例子看来,似乎没有任何作用,其实 boost::weak_ptr 主要用在软件架构设计中,可以在基类(此处的基类并非抽象基类,而是指继承于抽象基类的虚基类)中定义一个 boost::weak_ptr,用于指向子类的 boost::shared_ptr,这样基类仅仅观察自己的 boost::weak_ptr 是否为空就知道子类有没对自己赋值了,而不用影响子类 boost::shared_ptr 的引用计数,用以降低复杂度,更好的管理对象。


8、boost::intrusive_ptr

boost::intrusive_ptr属于 boost 库,定义在 namespace boost 中,包含头文件 #includee23b372897c2668fffd6001e3ccdaabb 便可以使用。

讲完如上 6 种智能指针后,对于一般程序来说 C++ 堆内存管理就够用了,现在有多了一种 boost::intrusive_ptr,这是一种插入式的智能指针,内部不含有引用计数,需要程序员自己加入引用计数,不然编译不过(⊙﹏⊙b汗)。个人感觉这个智能指针没太大用处,至少我没用过。有兴趣的朋友自己研究一下源代码哦J。


三、总结

如上讲了这么多智能指针,有必要对这些智能指针做个总结:

1、在可以使用 boost 库的场合下,拒绝使用 std::auto_ptr,因为其不仅不符合 C++ 编程思想,而且极容易出错[2]。

2、在确定对象无需共享的情况下,使用 boost::scoped_ptr(当然动态数组使用 boost::scoped_array)。

3、在对象需要共享的情况下,使用 boost::shared_ptr(当然动态数组使用 boost::shared_array)。

4、在需要访问 boost::shared_ptr 对象,而又不想改变其引用计数的情况下,使用 boost::weak_ptr,一般常用于软件框架设计中。

5、最后一点,也是要求最苛刻一点:在你的代码中,不要出现 delete 关键字(或 C 语言的 free 函数),因为可以用智能指针去管理。

以上就是C++ 智能指针详解的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Article précédent:Inlining de fonction CArticle suivant:Inlining de fonction C