Der unique_ptr des Smart Pointers (ausführliche Erklärung)

Unique_ptr des intelligenten Zeigers

Zeigerstellung und Initialisierung

Codebeispiel:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr2 = new int(11);  
    unique_ptr<int> ptr = make_unique<int>(10), ptr1(ptr2);  
    cout << *ptr1 << endl;  
    cout << *ptr2 << endl;  
} 

 

Betriebsergebnis:

 

Wir sehen, dass es zwei Möglichkeiten gibt, den Zeiger unique_ptr zu initialisieren:

① Rufen Sie make_unique <Typ> (Wert) auf, um einen Wert in der Definition zuzuweisen.

② Wie shared_ptr und auto_ptr kann der durch Aufrufen von new / new [] zurückgegebene Zeiger zum Initialisieren des unique_ptr-Zeigers verwendet werden.

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int[]> ptr(new int[2]{ 1,2 });  
    cout << ptr[0] << endl; // 输出1  
    cout << ptr[1] << endl; // 输出2  
}  

 

Die folgende Initialisierungsmethode ist jedoch falsch:

① Sie können unique_ptr, shared_ptr, auto_ptr und schwaches_ptr nicht verwenden, um den Zeiger unique_ptr zu initialisieren / zuzuweisen.

Codebeispiel:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    shared_ptr<int> ptr = make_shared<int>(10);  
    weak_ptr<int> ptr1(ptr);  
    auto_ptr<int> ptr2(new int(11));  
    unique_ptr<int> ptr3(ptr), ptr4(ptr1), ptr5(ptr2);  // 错误的初始化方式
} 

 

falscher Grund:

 

② Versuchen Sie, den Zeiger unique_ptr nicht zuzulassen, um den Zeiger beizubehalten, der mit dem neuen Rückgabewert initialisiert wurde.

Codebeispiel:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr = new int(10);  
    unique_ptr<int> ptr1(ptr); // 可以,但最好不要这样  
    unique_ptr<int> ptr2(new int(11)); // 这样最好  
}  

 

Die Vorteile davon:

unique_ptr betont die Eindeutigkeit im gesamten Bereich, was bedeutet, dass "in seinem Bereich nur ein Zeiger vorhanden ist, um diesen Heapspeicherplatz zu verwalten", wenn der neue Rückgabewert initialisiert wird. int * type pointer Zum Initialisieren von unique_ptr die folgenden Situationen kann auftreten:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr = new int(10);  
    unique_ptr<int> ptr1(ptr); // 使用ptr初始化unique_ptr指针  
    shared_ptr<int> ptr2(ptr); // 又使用ptr初始化shared_ptr指针  
}  

 

Fehlermeldung:

(Fehleraufforderung des Speichers wiederholte Löschfreigabe)

 

Der Grund dafür ist, dass nach der Initialisierung des unique_ptr-Zeigers auf den ptr-Zeiger das Ergebnis darin besteht, dass der unique_ptr-Zeiger und der ptr-Zeiger gemeinsam den Heap-Speicherplatz beibehalten, auf den ptr zeigt. Zu diesem Zeitpunkt wird ein wichtiges Problem entdeckt. Nachdem ptr dem Zeiger unique_ptr zugewiesen wurde, können auch andere Zeigertypen initialisiert werden ", wodurch der" Zeiger unique_ptr in seinem Bereich "ernsthaft verletzt wird. Ich hoffe, dass diese Situation nicht so oft wie möglich auftritt, da sonst der Speicher das Programm wiederholt freigibt und ein Absturz auftritt, weil ptr versehentlich erneut verwendet wird.

Vorsichtsmaßnahmen beim Zuweisen von unique_ptr

① Wenn Sie den Parameter unique_ptr an die Funktion übergeben, müssen Sie "Referenzübergabe" verwenden.

⑴ Beispiele für korrekten Code:

#include <iostream>  
#include <memory>  
using namespace std;  
  
void ShowInf(unique_ptr<int>& obj)  // 引用传参
{  
    cout << *obj << endl;  
}  
  
int main()  
{  
    unique_ptr<int> ptr = make_unique<int>(10);  
    ShowInf(ptr); // 输出10  
}  

 

⑵ Warum können Sie die Wertübertragung nicht zur Übertragung von Funktionsparametern verwenden?

Wir wissen, dass "unique_ptr alle Verhaltensweisen verbietet, die Speicherbesitz mit ihm teilen möchten." Bedeutet die Wertübertragung nicht, dass die Adresse, auf die der Zeiger zeigt, in eine "temporäre Zeigervariable, deren Gültigkeitsbereich nur innerhalb der Funktion liegt" kopiert wird? Da es sich um eine Kopie handelt, bedeutet dies, dass jeder das Eigentum an diesem Speicherbereich durch unique_ptr teilen möchte. Dies ist eine streng verbotene Handlung!

② Verwenden Sie den von new zurückgegebenen Heap-Bereichszeiger, um den unique_ptr-Zeiger während der Definition zu initialisieren. Nach Abschluss der Definition kann der von new zurückgegebene Heap-Bereichszeiger jedoch nicht dem unique_ptr-Zeiger zugewiesen werden.

Beispiele für Fehlercodes:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr1 = new int(11);  
    unique_ptr<int> ptr(nullptr);  
    //ptr = ptr1; // 错误代码  
}  

 

Fehlermeldung:

 

Richtiger Code:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr1 = new int(11);  
    unique_ptr<int> ptr(nullptr);  
    ptr = make_unique<int>(10);  
}  

 

Die einzige Option, um unique_ptr einen Wert zuzuweisen: Verwenden Sie make_unique <Typ> (Wert). Tatsächlich verwendet die unterste Ebene von make_unique auch new, um dynamisch Speicherplatz anzuwenden und einen Zeiger vom Typ unique_ptr zurückzugeben.

③ Aufgrund der eindeutigen Eigenschaften von unique_ptr kann es nur während seines Lebenszyklus auf einen Speicherbereich verweisen, sodass intelligente Zeiger vom Typ unique_ptr nicht kopiert werden können! Da unique_ptr nicht kopiert werden kann, kann es nur verschoben werden. Daher können wir keine Kopie des unique_ptr-Objekts über den Kopierkonstruktor oder den Zuweisungsoperator erstellen. Wenn Sie einen beliebigen Kopierkonstruktor unique_ptr oder einen überladenen Zuweisungsoperator verwenden, teilt Ihnen der Compiler mit, dass die entsprechende Elementfunktion gelöscht wurde. Rufen Sie sie nicht erneut auf.

// 编译错误 : unique_ptr 不能复制  
std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error  
  
// 编译错误 : unique_ptr 不能复制  
taskPtr = taskPtr2; //compile error  

 

Analyse der Mitgliedsfunktion unique_ptr

① Funktion abrufen: Gibt die Zeigeradresse zurück, die von unique_ptr verwaltet wird

⑴ Funktion:

Gibt die von unique_ptr verwaltete Zeigeradresse zurück.

⑵ Codebeispiel:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr1 = new int(11);  
    unique_ptr<int> ptr(nullptr);  
    ptr = make_unique<int>(10);  
  
    cout << *ptr.get() << endl; // 返回10  
}  

 

② Freigabefunktion: Geben Sie den Besitz des zugehörigen Originalzeigers frei und geben Sie den Originalzeiger zurück

⑴ Funktion:

Geben Sie den Besitz des zugehörigen Originalzeigers frei und geben Sie den Originalzeiger zurück.

⑵ Funktionsbeispiel:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int> ptr = make_unique<int>(10);  
    int* ptr1 = ptr.release();  // 返回与unique_ptr类型相同类型的指针
    if (ptr == nullptr)  
    {  
        cout << "unique_ptr类型的指针ptr已被置空,释放了内存的控制权" << endl;  
    }  
    if (ptr1 != nullptr)  
    {  
        cout << "原本unique_ptr指向的内存空间并没有被释放掉" << endl;  
    }  
}  

 

Ausgabeergebnis:

 

③ Reset-Funktion: Geben Sie den spitzen Speicher frei und geben Sie den Besitz dieses Speichers frei

⑴ Funktion:

Geben Sie den spitzen Speicher frei und geben Sie den Besitz dieses Speichers frei.

⑵ Codebeispiel:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int> ptr = make_unique<int>(10);  
    cout << "ptr复位前:" << *ptr << endl;  
    ptr.reset(new int(100)); // 使得ptr指向一块新的内存空间,同时释放掉原来指向的内存空间  
    cout << "ptr复位后:" << *ptr << endl;  
    ptr.reset(nullptr); //与ptr.reset()效果等价,都是使得ptr指向nullptr  
    cout << "ptr是否被置空:" << (ptr == nullptr) << endl;  
} 

. 

Betriebsergebnis:

 

⑶ Ergebnisanalyse:

Tatsächlich ist die Reset-Member-Funktion hier die gleiche wie die Reset-Member-Funktion von shared_ptr, da sie den Speicherplatz ändert, auf den der Zeiger zeigt. Der Unterschied besteht jedoch darin, dass nur ein unique_ptr-Zeiger auf diesen im Heap geöffneten Speicherbereich zeigt Bereich, wenn Wenn der Zeiger auf einen anderen Speicherplatz zeigt, wird der ursprüngliche Speicherplatz freigegeben. Daher nenne ich "die Funktion, die die Rücksetzfunktion des Zeigers unique_ptr besitzt", oft "die Zerstörungsfunktion des Zeigers, dh die legendäre selbstzerstörerische Funktion".

 

④ Swap-Funktion: Tauschen Sie zwei unique_ptr-Zeiger auf den Bereich aus (Exchange-Adresse).

⑴ Funktionsfunktion:

Tauschen Sie zwei unique_ptr-Zeiger auf den Bereich aus (Austauschadresse)

⑵ Codebeispiel:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int> ptr = make_unique<int>(10), ptr1(nullptr);  
    cout << "交换前:" << endl;  
    cout << "ptr:" << ptr.get() << endl;  
    cout << "ptr1:" << ptr1.get() << endl;  
    ptr.swap(ptr1);  
    cout << "交换后:" << endl;  
    cout << "ptr:" << ptr.get() << endl;  
    cout << "ptr1:" << ptr1.get() << endl;  
}  

 

Betriebsergebnis:

 

Das Ergebnis zeigt, dass die Adressen, auf die die beiden Zeiger unique_ptr <int> von ptr und ptr1 zeigen, ausgetauscht wurden.

⑤ Funktion get_deleter: Gibt einen benutzerdefinierten Löscher zurück

⑴ Funktion:

Wir wissen, dass, wenn ein intelligenter Zeiger den spitzen Speicherplatz beendet oder freigibt (die Memberfunktion unique_ptr :: reset () wird ausgeführt), der Deleter aufgerufen wird, um den Heapspeicherplatz zu löschen, auf den der Zeiger zeigt. Wenn diese Operation ausgeführt wird Durch die Systemsteuerung können wir nur wissen, ob der Speicher effektiv freigegeben wurde, oder wir möchten die "Speicherfreigabeleistung" in unseren eigenen Händen steuern (zum Beispiel: Wir können zählen, wie oft das System über den Zeiger unique_ptr freigegeben wurde Speicher ... und andere benutzerfreundlichere Vorgänge). Dies ist hilfreich, um einige Informationen zur Speicherfreigabe zu zählen und den Code basierend auf diesen Informationen zu optimieren, um den Code zu optimieren und die Betriebseffizienz des Codes zu maximieren.

⑵ Codebeispiel:

Der folgende Code zeigt "wie oft das System Speicher über den ptr-Zeiger freigegeben hat":

#include <iostream>  
#include <memory>  
using namespace std;  
  
class deleter  
{  
private:  
    int count;  
public:  
    deleter() :count(0) {};  
    void ShowInf()  
    {  
        cout << "调用此删除器的次数为" << this->count << endl;  
    }  
    template <typename T>  
    void operator()(T* ptr)  
    {  
        this->count++;  
        cout << "调用删除器的次数为" << this->count << endl;  
        delete ptr;  
    }  
};  
  
int main()  
{  
    unique_ptr<int, deleter> object_a(new int(1));  
    object_a.reset(new int(2));  
    object_a.get_deleter().ShowInf();  
}  

 

Betriebsergebnis:

 

Das erste Mal haben wir die Reset-Member-Funktion aufgerufen, um den Speicherplatz zu ändern, auf den ptr zeigt, und den Speicherbereich freigegeben, auf den ptr ursprünglich zeigte. Das zweite Mal haben wir das anonyme Objekt der Deleter-Klasse aufgerufen, das von get_deleter () parameterless zurückgegeben wurde Mitgliedsfunktion (temporäres Objekt) zum Aufrufen der gemeinsam genutzten Mitgliedsfunktion der Klasse (der externen Schnittstelle der Klasse) zur Anzeige von "Bisher, wie oft das System den Heapspeicher über den ptr-Zeiger freigegeben hat"; das dritte Mal liegt daran Der Lebenszyklus des ptr-Zeigers muss übergeben werden. Rufen Sie den Deleter von ptr auf, um den von ptr angezeigten Speicherplatz freizugeben. Hier haben wir den Deleter so angepasst, dass die Löschung des von ptr angezeigten Speichers in unseren eigenen Händen bleibt. Wenn ptr den Speicher freigibt, rufen wir unseren Funktor delete () auf.

Darüber hinaus können wir dasselbe Löschobjekt auch in mehreren Smart-Zeigern vom Typ unique_ptr verwenden, um zu zählen, wie oft mehrere oder alle Smart-Zeiger vom Typ unique_ptr den Speicherbereich während der Codeausführung löschen. Das Codebeispiel lautet wie folgt:

#include <iostream>  
#include <memory>  
using namespace std;  
  
template <typename T>  
class deleter  
{  
private:  
    int count;  
public:  
    deleter() :count(0) {};  
    void ShowInf()  
    {  
        cout << "调用此删除器的次数为" << this->count << endl;  
    }  
    void operator()(T* ptr)  
    {  
        this->count++;  
        cout << "调用删除器的次数为" << this->count << endl;  
        delete ptr;  
    }  
};  
  
int main()  
{  
    unique_ptr<int, deleter<int> > object_a(new int(1));  
    unique_ptr<int, deleter<int>& > object_b(new int(3), object_a.get_deleter());  
}  

 

Die Ergebnisse sind wie folgt:

 

Wir können sehen, dass object_a und object_b einen Deleter gemeinsam haben, sodass wir zählen können, wie oft die beiden unique_ptr-Zeiger von object_a und object_b Speicher freigeben. Wir können dies verallgemeinern auf: "Zählen Sie, wie oft der Zeiger vom Typ unique_ptr Speicher im gesamten Programm freigibt".

Sie können auch das folgende Codeformat verwenden, um zu zählen, wie oft das System Speicher über die Zeiger object_a und object_b freigibt:

#include <iostream>  
#include <memory>  
using namespace std;  
  
template <typename T>  
class deleter  
{  
private:  
    int count;  
public:  
    deleter() :count(0) {};  
    void ShowInf()  
    {  
        cout << "调用此删除器的次数为" << this->count << endl;  
    }  
    void operator()(T* ptr)  
    {  
        this->count++;  
        cout << "调用删除器的次数为" << this->count << endl;  
        delete ptr;  
    }  
};  
  
int main()  
{  
    deleter<int> del;  
    unique_ptr<int, deleter<int>& > object_a(new int(1), del);  // 传入del这个deleter类型的对象本身
    unique_ptr<int, deleter<int>& > object_b(new int(3), del);  // 传入del这个deleter类型的对象本身
}  

 

Wie übertrage ich den Besitz des unique_ptr-Objekts?

Codebeispiel:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int> ptr(new int(10));  
    cout << "调用移动语义前:" << endl;  
    cout << "ptr:" << ptr.get() << endl;  
    unique_ptr<int> ptr1 = move<unique_ptr<int>& >(ptr);  
    cout << "ptr指向是否为空:" << (ptr == nullptr) << endl;  
    cout << "ptr1指向的元素为" << *ptr1 << endl;  
    cout << "使用移动语义后:" << endl;  
    cout << "ptr:" << ptr.get() << endl;  
    cout << "ptr1:" << ptr1.get() << endl;  
}  

 

Betriebsergebnis:

 

Wir können aus den Ergebnissen ersehen: Das Ergebnis der Verschiebungssemantik besteht darin, die in einem unique_ptr gespeicherte Adresse auf einen anderen unique_ptr-Zeiger zu übertragen und den Zeiger des Quelltyps unique_ptr (= nullptr) auf Null zu setzen. Dies ist sicherer, aber wir müssen überprüfen, ob die folgenden Zeiger nicht leer sind, bevor wir Zeiger für alle Fälle verwenden.

Ich denke du magst

Origin blog.csdn.net/weixin_45590473/article/details/113101912
Empfohlen
Rangfolge