Heim  >  Artikel  >  Backend-Entwicklung  >  Detaillierte Erläuterung der C++-Smartpointer

Detaillierte Erläuterung der C++-Smartpointer

黄舟
黄舟Original
2017-02-06 13:41:491711Durchsuche

Detaillierte Erklärung der C++-Smart-Pointer


1. Einführung

Da die C++-Sprache keinen automatischen Speicherrecyclingmechanismus hat, müssen Programmierer neue verwenden Speicher jedes Mal manuell löschen. Es kommt nicht selten vor, dass Programmierer das Löschen vergessen, der Vorgang ist zu kompliziert, es findet letztendlich kein Löschvorgang statt und Ausnahmen führen dazu, dass das Programm vorzeitig beendet wird, ohne den Löschvorgang auszuführen.

Die Verwendung intelligenter Zeiger kann solche Probleme wirksam lindern. In diesem Artikel wird hauptsächlich die Verwendung intelligenter Zeiger erläutert. Einschließlich: std::auto_ptr, boost::scoped_ptr, boost::shared_ptr, boost::scoped_array, boost::shared_array, boost::weak_ptr, boost::intrusive_ptr. Sie denken vielleicht: Sind so viele intelligente Hinweise wirklich notwendig, um die neuen und gelöschten Matching-Probleme zu lösen? Nachdem ich diesen Artikel gelesen habe, denke ich, dass Sie die Antwort natürlich im Kopf haben werden.

Die folgenden sieben Arten von Smart Pointern (smart_ptr) werden der Reihe nach erklärt.

2. Spezifische Verwendung


1. Für den Compiler ist ein intelligenter Zeiger eigentlich ein Stapelobjekt, kein Zeigertyp Wenn die Lebensdauer des Stapelobjekts bald zu Ende geht, gibt der Smart Pointer den von ihm verwalteten Heap-Speicher über den Destruktor frei. Alle intelligenten Zeiger sind mit dem Operator „operator->“ überladen, der direkt einen Verweis auf das Objekt zurückgibt und zum Bedienen des Objekts verwendet wird. Die ursprüngliche Methode für den Zugriff auf intelligente Zeiger verwendet den Operator „.“

Um auf den im Smart Pointer enthaltenen Rohzeiger zuzugreifen, können Sie die Funktion get() verwenden. Da ein Smart Pointer ein Objekt ist, ist if (my_smart_object) immer wahr. Um festzustellen, ob der Rohzeiger des Smart Pointers null ist, müssen Sie Folgendes ermitteln: if (my_smart_object.get()).

Der Smart Pointer enthält die Methode reset() Wenn keine Parameter übergeben werden (oder NULL übergeben wird), gibt der Smart Pointer den aktuell verwalteten Speicher frei. Wenn ein Objekt übergeben wird, gibt der Smart Pointer das aktuelle Objekt frei, um das neu übergebene Objekt zu verwalten.


Wir schreiben eine Testklasse zur Unterstützung der 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 gehört zu STL. Natürlich kann es im Namespace std verwendet werden, indem die Header-Datei #include eingefügt wird. std::auto_ptr kann bequem ein einzelnes Heap-Speicherobjekt verwalten.

Wir beginnen mit der Codeanalyse:

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)

Das Ausführungsergebnis ist:

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

Das Obige ist der Code für die normale Verwendung von std::auto_ptr. Auf jeden Fall müssen wir das verdammte Löschen nicht explizit verwenden.

Tatsächlich hielten die guten Zeiten nicht lange an:

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();        // 崩溃
}
}

Der endgültige Code ist wie oben. Verursacht einen Absturz. Der obige Code entspricht absolut den C++-Programmierideen, stürzt jedoch tatsächlich ab. Nachdem wir dem Quellcode von std::auto_ptr gefolgt sind, sehen wir, dass der Übeltäter „my_memory2“ ist = my_memory". In dieser Codezeile übernimmt my_memory2 vollständig my_memory. Speicherverwaltungseigentum, was dazu führt, dass my_memory baumelt und bei der letzten Verwendung einen Absturz verursacht.

Wenn Sie also std::auto_ptr verwenden, dürfen Sie nicht den Operator „operator=" verwenden. Da es sich um eine Bibliothek handelt, dürfen Benutzer sie nicht verwenden, und sie wird nicht ausdrücklich abgelehnt [1], was etwas unerwartet ist.

Nachdem wir das erste Beispiel von std::auto_ptr gelesen haben, das nicht lange anhielt, schauen wir uns ein anderes an:

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

Das Ausführungsergebnis ist:

Haben Sie etwas Ungewöhnliches gesehen? Das von uns erstellte Objekt wurde nicht zerstört und „~Simple: 1“ wurde nicht ausgegeben, was zu einem Speicherverlust führte. Wenn wir nicht möchten, dass my_memory weiterhin überlebt, rufen wir die Funktion release() auf, um den Speicher freizugeben, was zu einem Speicherverlust führt (in einem System mit begrenztem Speicher wird eine Rückgabe in Betracht gezogen, wenn my_memory zu viel Speicher beansprucht). Es wird sofort nach Abschluss der Verwendung zurückgegeben, und anstatt zu warten, bis my_memory seine Lebensdauer beendet, wird es zurückgegeben.

Simple: 1


Der korrekte Code sollte sein:

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

oder

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

Es stellt sich heraus, dass die release()-Funktion von std::auto_ptr lediglich den Speicherbesitz aufgibt, was offensichtlich nicht im Einklang mit den Programmierideen von C++ steht.

Zusammenfassung: std::auto_ptr kann zum Verwalten des Speichers eines einzelnen Objekts verwendet werden. Beachten Sie jedoch bitte die folgenden Punkte:

(1) Versuchen Sie, nicht „operator=“ zu verwenden ". Wenn es verwendet wird, verwenden Sie das vorherige Objekt nicht erneut.

(2) Denken Sie daran, dass die Funktion release() das Objekt nicht freigibt, sondern nur den Besitz zurückgibt.

(3) std::auto_ptr sollte am besten nicht als Parameter übergeben werden (Leser können ihren eigenen Code schreiben, um festzustellen, warum nicht).

(4) Aufgrund des „operator="-Problems von std::auto_ptr können die von ihm verwalteten Objekte nicht in Container wie std::vector abgelegt werden.

(5) ...

Es gibt so viele Einschränkungen bei der Verwendung eines std::auto_ptr. Es kann nicht zum Verwalten von Heap-Speicher-Arrays verwendet werden. Das sollte sein: Woran denkst du jetzt? Ich denke auch, dass es eines Tages ziemlich viele Einschränkungen geben wird, wenn du nicht aufpasst.

Da std::auto_ptr viele Probleme verursacht hat, stimmen einige Designs nicht sehr mit den C++-Programmierideen überein, sodass die folgenden Boost-Smart-Pointer ausgelöst wurden Lösen Sie die oben genannten Probleme.

Lass uns weiter nach unten schauen.

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)!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Vorheriger Artikel:Inlining von C++-FunktionenNächster Artikel:Inlining von C++-Funktionen