C/C++-Studie stellt fest, dass die Ressourcenakquisition ein Initialisierungsverständnis (RAII) ist

1. Überblick über RAII

        Vielleicht ist ein besserer Name für RAII "bereichsgebundene Ressource", da Sie die Lebensdauer der Ressource an die Lebensdauer der lokalen Variablen binden und die Lebensdauer der lokalen Variablen endet, wenn sie den Bereich verlässt.

        Modernes C++ vermeidet die Verwendung von Heap-Speicher so weit wie möglich, indem es Objekte auf dem Stack deklariert. Wenn eine Ressource zu groß für den Stack ist, sollte sie einem Objekt gehören . Wenn ein Objekt initialisiert wird, erwirbt es die Ressourcen, die es besitzt. Das Objekt ist dann für die Freigabe der Ressource in seinem Destruktor verantwortlich. Das besitzende Objekt selbst wird auf dem Stack deklariert. Das Prinzip, dass Objekte eigene Ressourcen besitzen, wird auch als „Resource Acquisition is Initialization“ oder RAII bezeichnet.

        Wenn das Stapelobjekt, das die Ressource besitzt, den Gültigkeitsbereich verlässt, wird sein Destruktor automatisch aufgerufen. Auf diese Weise ist die Garbage Collection in C++ eng mit der Lebensdauer von Objekten verbunden und deterministisch. Ressourcen werden immer an einem bekannten Punkt im Programm freigegeben, und Sie können diesen Punkt steuern. Nur deterministische Destruktoren wie in C++ können Speicher- und Nicht-Speicherressourcen gleichermaßen behandeln.

        RAII garantiert, dass Ressourcen für alle Funktionen verfügbar sind, die möglicherweise auf das Objekt zugreifen (die Ressourcenverfügbarkeit ist eine Klasseninvariante, wodurch redundante Laufzeittests eliminiert werden). Es garantiert auch, dass alle Ressourcen in umgekehrter Reihenfolge des Erwerbs am Ende der Lebensdauer ihres steuernden Objekts freigegeben werden. Wenn die Ressourcenakquisition fehlschlägt (der Konstruktor wird abnormal beendet), werden alle Ressourcen, die von jedem vollständig konstruierten Member und zugrunde liegenden Unterobjekt erworben wurden, in der umgekehrten Reihenfolge der Initialisierung freigegeben. Dies nutzt Kernfunktionen der Sprache (Objektlebensdauer, Scope-Exit, Initialisierungsreihenfolge und Stack-Unwinding), um Ressourcenlecks zu eliminieren und Ausnahmesicherheit zu gewährleisten. Ein anderer Name für diese Technologie ist Scope Binding Resource Management (SBRM), nach dem grundlegenden Anwendungsfall endet der Lebenszyklus des RAII-Objekts aufgrund des Scope-Austritts.

        RAII kann wie folgt zusammengefasst werden:

        Kapseln Sie jede Ressource in eine Klasse, wobei der Konstruktor die Ressource übernimmt und alle Klasseninvarianten festlegt, eine Ausnahme auslöst, wenn dies nicht möglich ist, und der Destruktor die Ressource freigibt und niemals eine Ausnahme auslöst.

        Eine Ressource wird immer durch eine Instanz einer RAII-Klasse verwendet, entweder mit einer automatischen Speicherdauer oder einer temporären Lebensdauer oder einer Lebensdauer, die durch die Lebensdauer eines automatischen oder temporären Objekts begrenzt ist.

        Die RAII-Klasse besteht aus drei Teilen:

        Die Ressource wird im Destruktor verworfen (z. B. Schließen der Datei)

        Instanzen der Klasse werden dem Stapel zugeordnet

        Ressourcen (z. B. offene Dateien) im Konstruktor abrufen. Dieser Teil ist optional.

2. Beispiel 1: Stellen Sie sicher, dass die Instanz auf dem Stapel zugewiesen ist

class OpenFile {
public:
    OpenFile(const char* filename){
        //失败则抛出异常
        _file.open(filename);
    }
    
    ~OpenFile(){
        _file.close();
    }
    
    std::string readLine() {
        return _file.readLine();
    }
    
private:
    File _file;
};


OpenFile f("boo.txt");
//异常安全,并且没有必要进行关闭
loadFromFile(f);

         Stellen Sie sicher, dass Instanzen auf dem Stack und nicht auf dem Heap zugewiesen werden

std::string firstLineOf(const char* filename){
    OpenFile f("boo.txt"); //stack allocated
    return f.readLine();
    //文件在这里关闭。 `f` 超出范围并运行析构函数。
}

std::string firstLineOf(const char* filename){
    OpenFile* f = new OpenFile("boo.txt"); //heap allocated
    return f->readLine();
    //析构函数永远不会运行,因为 `f` 永远不会被删除
}

3. Beispiel 2: Schlechte und gute Beispiele

std::mutex m;
 
//这是一个坏的示例
void bad() 
{
    m.lock();                    // acquire the mutex
    f();                         // if f() throws an exception, the mutex is never released
    if(!everything_ok()) return; // early return, the mutex is never released
    m.unlock();                  // if bad() reaches this statement, the mutex is released
}

//这是一个好的示例
void good()
{
    std::lock_guard<std::mutex> lk(m); // RAII class: mutex acquisition is initialization
    f();                               // if f() throws an exception, the mutex is released
    if(!everything_ok()) return;       // early return, the mutex is released
}                                      // if good() returns normally, the mutex is released

4. Beispiel 3: Ein Beispiel für die Verwendung von Smart Pointern

1. Einfache Objekte

        Das folgende Beispiel zeigt ein einfaches Objekt w. Es wird im Funktionsbereich auf dem Stack deklariert und am Ende des Funktionsblocks zerstört. Das Objekt w besitzt keine Ressourcen (z. B. vom Heap zugewiesenen Speicher). Sein einziges Mitglied g selbst wird auf dem Stack deklariert und verlässt zusammen mit w den Geltungsbereich w. Im Widget-Destruktor ist kein spezieller Code erforderlich.

class widget {
private:
    gadget g;   // 生命周期自动绑定到封闭对象
public:
    void draw();
};

void functionUsingWidget () {
    widget w;   // 生命周期自动绑定到封闭范围构造 w,包括 w.g 成员
    // ...
    w.draw();
    // ...
} // w 和 w.g 自动异常安全的自动销毁和释放,就像“finally { w.dispose(); w.g.dispose(); }”

2. Ressourcen manuell freigeben

        Im folgenden Beispiel besitzt w die Speicherressource, daher muss es Code in seinem Destruktor enthalten, um den Speicher freizugeben.

class widget
{
private:
    int* data;
public:
    widget(const int size) { data = new int[size]; } // acquire
    ~widget() { delete[] data; } // release
    void do_something() {}
};

void functionUsingWidget() {
    widget w(1000000);  // 生命周期自动绑定到封闭范围构造 w,包括 w.data 成员
    w.do_something();

} //w 和 w.data 的自动销毁和释放

3. Verwenden Sie intelligente Zeiger

        Ab C++11 gibt es eine bessere Möglichkeit, das obige Beispiel zu schreiben: die Verwendung von intelligenten Zeigern aus der Standardbibliothek. Ein intelligenter Zeiger behandelt die Zuweisung und Löschung des Speichers, den er besitzt. Die Verwendung von intelligenten Zeigern macht einen expliziten Destruktor in der Widget-Klasse überflüssig.

#include <memory>
class widget
{
private:
    std::unique_ptr<int[]> data;
public:
    widget(const int size) { data = std::make_unique<int[]>(size); }
    void do_something() {}
};

void functionUsingWidget() {
    widget w(1000000);  // 生命周期自动绑定到封闭范围构造 w,包括 w.data 小工具成员
    // ...
    w.do_something();
    // ...
} // w 和 w.data 的自动销毁和释放

        Indem Sie intelligente Zeiger für die Speicherzuweisung verwenden, können Sie die Möglichkeit von Speicherlecks ausschließen. Dieses Modell gilt für andere Ressourcen wie Dateihandles oder Sockets. Innerhalb einer Klasse können Sie Ihre eigenen Ressourcen auf ähnliche Weise verwalten.

        C++ wurde entwickelt, um sicherzustellen, dass Objekte zerstört werden, wenn sie den Gültigkeitsbereich verlassen. Das heißt, sie werden beim Verlassen des Blocks in umgekehrter Baureihenfolge zerstört. Wenn ein Objekt zerstört wird, werden seine Basen und Mitglieder in einer bestimmten Reihenfolge zerstört. Objekte, die im globalen Geltungsbereich außerhalb eines Blocks deklariert sind, können Probleme verursachen. Das Debuggen kann schwierig sein, wenn der Konstruktor des globalen Objekts eine Ausnahme auslöst.

Supongo que te gusta

Origin blog.csdn.net/bashendixie5/article/details/127205389
Recomendado
Clasificación