C++ Speicherklassen

Speicherklassen definieren den Gültigkeitsbereich (Sichtbarkeit) und die Lebensdauer von Variablen/Funktionen in C++-Programmen. Diese Spezifizierer werden vor den Typ gestellt, den sie modifizieren. Die folgende Liste zeigt die in C++-Programmen verfügbaren Speicherklassen:

  • auto: Dies ist der Standard-Speicherklassenspezifizierer und kann normalerweise weggelassen werden. Variablen, die mit auto angegeben werden, haben automatische Speicherdauer, d. h. ihre Lebensdauer ist auf den Block beschränkt, in dem sie definiert sind. auto-Variablen werden üblicherweise auf dem Stack zugewiesen.

  • register: Wird verwendet, um dem Compiler vorzuschlagen, die Variable in einem CPU-Register zu speichern, um die Zugriffsgeschwindigkeit zu verbessern. In C++11 und später ist register ein veraltetes Feature und hat keine praktische Wirkung mehr.

  • static: Wird verwendet, um Variablen oder Funktionen mit statischer Speicherdauer zu definieren, deren Lebensdauer sich über die gesamte Laufzeit des Programms erstreckt. Innerhalb einer Funktion bleibt der Wert einer statischen Variable zwischen Funktionsaufrufen unverändert. Innerhalb einer Datei oder im globalen Gültigkeitsbereich haben statische Variablen interne Bindung, und auf sie kann nur in der Datei zugegriffen werden, in der sie definiert sind.

  • extern: Wird verwendet, um Variablen oder Funktionen mit externer Bindung zu deklarieren, die zwischen mehreren Dateien gemeinsam genutzt werden können. Standardmäßig haben globale Variablen und Funktionen die Speicherklasse extern. Die Verwendung von extern in einer Datei, um eine globale Variable oder Funktion zu deklarieren, die in einer anderen Datei definiert ist, ermöglicht die dateiübergreifende gemeinsame Nutzung.

  • mutable (C++11): Wird verwendet, um Membervariablen in einer Klasse zu ändern, sodass ihre Werte in const-Memberfunktionen geändert werden können. Es wird häufig für Daten wie Caches oder Zähler verwendet, die in einem const-Kontext geändert werden müssen.

  • thread_local (C++11): Wird verwendet, um Variablen mit thread-lokaler Speicherdauer zu definieren; jeder Thread hat seine eigene unabhängige Kopie. Die Lebensdauer einer thread-lokalen Variablen entspricht der Lebensdauer des Threads.

Seit C++17 ist das Schlüsselwort auto kein C++-Speicherklassenspezifizierer mehr, und das Schlüsselwort register wurde als veraltet eingestuft.

Die darin enthaltenen Speicherklassenspezifizierer bieten Programmierern eine Möglichkeit, die Lebensdauer und Sichtbarkeit von Variablen und Funktionen zu steuern.

Eine sinnvolle Verwendung von Speicherklassenspezifizierern kann die Wartbarkeit und Leistung von Programmen verbessern.

Seit C++11 hat register seine ursprüngliche Rolle verloren, während mutable und thread_local neu eingeführte Features sind, die zur Lösung spezifischer Programmierprobleme dienen.

Unten ist ein Beispiel, das verschiedene Speicherklassenspezifizierer demonstriert:

Beispiel

#include <iostream>

// Globale Variable mit externer Bindung, Standard-Speicherklasse ist extern
int globalVar;

void function() {
    // Lokale Variable mit automatischer Speicherdauer, Standard-Speicherklasse ist auto
    auto int localVar = 10;

    // Statische Variable mit statischer Speicherdauer, Lebensdauer erstreckt sich über das gesamte Programm
    static int staticVar = 20;

    const int constVar = 30; // const-Variable hat standardmäßig statische Speicherdauer

    // Der Versuch, eine const-Variable zu ändern, ist ein Kompilierungsfehler
    // constVar = 40;

    // mutable-Membervariable, kann in const-Memberfunktionen geändert werden
    class MyClass {
    public:
        mutable int mutableVar;

        void constMemberFunc() const {
            mutableVar = 50; // Ermöglicht das Ändern einer mutable-Membervariable
        }
    };

    // Thread-lokale Variable, jeder Thread hat seine eigene unabhängige Kopie
    thread_local int threadVar = 60;
}

int main() {
    extern int externalVar; // Eine Variable mit externer Bindung deklarieren

    Funktion();

    return 0;
}

auto-Speicherklasse

Seit C++ 11,autoDas Schlüsselwort wird in zwei Situationen verwendet: bei der Deklaration einer Variable, wobei der Typ der Variable automatisch anhand des Initialisierungsausdrucks abgeleitet wird; bei der Deklaration einer Funktion als Platzhalter für den Rückgabewert der Funktion.

Im C++98-Standard wurde das Schlüsselwort auto für die Deklaration automatischer Variablen verwendet, aber da es selten verwendet und redundant war, wurde diese Verwendung in C++17 entfernt.

Automatische Ableitung des Typs der deklarierten Variable anhand des Initialisierungsausdrucks, zum Beispiel:

auto f=3.14; //double auto s("hello"); //const char* auto z = new auto(9); // int* auto x1 = 5, x2 = 5.0, x3='r';// Fehler: muss mit demselben Typ initialisiert werden

register-Speicherklasse

register ist eine Speicherklasse, die zum Deklarieren von Variablen verwendet wird und dem Compiler einen Hinweis gibt, diese Variablen für einen schnellen Zugriff in Registern zu speichern.

Die Verwendung des Schlüsselworts register kann die Ausführungsgeschwindigkeit eines Programms verbessern, da es die Anzahl der Speicherzugriffe reduziert.

Beachten Sie jedoch, dass die Speicherklasse register nur ein Hinweis ist; der Compiler kann sie ignorieren, weil moderne Compiler Code normalerweise automatisch optimieren und einen geeigneten Speicherort wählen.

Syntax:

register data_type variable_name;
  • registerEs ist ein Schlüsselwort für eine Speicherklasse, das verwendet wird, um dem Compiler einen Hinweis zu geben, Variablen in Registern zu speichern.
  • data_typeEs ist der Datentyp der Variable, der ein beliebiger gültiger C++-Datentyp sein kann.
  • variable_nameist der Name der Variable.
void loop() { register int i; for (i = 0; i < 1000; ++i) { // Schleifenkörper } }

Die register-Speicherklasse wird verwendet, um dem Compiler einen Hinweis zu geben, Variablen in Registern zu speichern, um die Zugriffsgeschwindigkeit zu verbessern. Aufgrund der automatischen Optimierungsfähigkeiten moderner Compiler ist die Verwendung des Schlüsselworts register jedoch nicht erforderlich und wird in der Praxis selten verwendet.

Im C++11-Standard ist das Schlüsselwort register kein Speicherklassenbezeichner mehr, sondern ein veraltetes Feature. Das bedeutet, dass in C++11 und späteren Versionen die Verwendung des Schlüsselworts register keine Auswirkung auf das Programm hat.

In C++ können Referenzen oder Zeiger verwendet werden, um die Zugriffsgeschwindigkeit zu verbessern, insbesondere beim Umgang mit großen Datenstrukturen.

static-Speicherklasse

staticDie Speicherklasse weist den Compiler an, lokale Variablen während der gesamten Lebensdauer des Programms bestehen zu lassen, ohne sie jedes Mal neu erstellen und zerstören zu müssen, wenn sie in den Gültigkeitsbereich eintreten und ihn verlassen. Daher kann die Verwendung von static zum Modifizieren einer lokalen Variablen den Wert der lokalen Variablen zwischen Funktionsaufrufen erhalten.

Der static-Modifizierer kann auch auf globale Variablen angewendet werden. Wenn static eine globale Variable modifiziert, beschränkt es den Gültigkeitsbereich der Variable auf die Datei, in der sie deklariert ist.

In C++ führt die Verwendung von static für ein Datenmember einer Klasse dazu, dass nur eine Kopie dieses Members von allen Objekten der Klasse gemeinsam genutzt wird.

Beispiel

#include <iostream> // Funktionsdeklaration void func(void); static int count = 10; /*Globale Variablen*/ int main() { while(count--) { func(); } return 0; } // Funktionsdefinition void func( void ) { static int i = 5; // Lokale statische Variable i++; std::cout << "Variable i ist" << i ; std::cout << ", Variable count ist" << count << std::endl; }

Wenn der obige Code kompiliert und ausgeführt wird, liefert er die folgenden Ergebnisse:

变量 i 为 6 , 变量 count 为 9
变量 i 为 7 , 变量 count 为 8
变量 i 为 8 , 变量 count 为 7
变量 i 为 9 , 变量 count 为 6
变量 i 为 10 , 变量 count 为 5
变量 i 为 11 , 变量 count 为 4
变量 i 为 12 , 变量 count 为 3
变量 i 为 13 , 变量 count 为 2
变量 i 为 14 , 变量 count 为 1
变量 i 为 15 , 变量 count 为 0

extern-Speicherklasse

externDie Speicherklasse wird verwendet, um eine Referenz auf eine globale Variable bereitzustellen, die für alle Programmdateien sichtbar ist. Wenn Sie 'extern' für eine Variable verwenden, die nicht initialisiert werden kann, verweist sie den Variablennamen auf eine zuvor definierte Speicherstelle.

Wenn Sie mehrere Dateien haben und eine globale Variable oder Funktion definieren, die in anderen Dateien verwendet werden kann, können Sie sie in anderen Dateien verwenden.extern, um eine Referenz auf die definierte Variable oder Funktion zu erhalten. Das kann man sich so vorstellen,externEs wird verwendet, um eine globale Variable oder Funktion in einer anderen Datei zu deklarieren.

Der extern-Modifikator wird normalerweise verwendet, wenn zwei oder mehr Dateien dieselben globalen Variablen oder Funktionen gemeinsam nutzen, wie unten gezeigt:

Erste Datei: main.cpp

Beispiel

#include <iostream> int count ; extern void write_extern(); int main() { count = 5; write_extern(); }

Zweite Datei: support.cpp

Beispiel

#include <iostream> extern int count; void write_extern(void) { std::cout << "Anzahl ist " << count << std::endl; }

Hier, in der zweiten Datei, wird dasexternSchlüsselwort verwendet, um count zu deklarieren, das bereits in der ersten Datei main.cpp definiert wurde. Nun kompilieren Sie diese beiden Dateien wie folgt:

$ g++ main.cpp support.cpp -o write

Dies erzeugtwriteausführbares Programm, versuchen Sie,writeauszuführen, es wird die folgenden Ergebnisse liefern:

$ ./write
Count is 5

mutable Speicherklasse

mutable ist ein Schlüsselwort, das verwendet wird, um Klassenmitgliedsvariablen zu ändern, damit sie in const-Memberfunktionen geändert werden können. Normalerweise können const-Memberfunktionen den Zustand eines Objekts nicht ändern, aber wenn eine Mitgliedsvariable als mutable deklariert ist, kann sie in const-Funktionen geändert werden.

Funktionen:

  • Ermöglicht Änderung:mutableMembervariablen könnenconstMemberfunktion wurde geändert.
  • Entwurfszweck: Er wird normalerweise in Szenarien verwendet, die Zustandsverwaltung erfordern, ohne den externen Zustand eines Objekts zu ändern, wie Caching, Lazy Evaluation usw.

Beispiel

#include <iostream>

class Example {
public:
    Example() : value(0), cachedValue(0) {}

    // const-Memberfunktion
    int getValue() const {
        return value; // const-Member lesen
    }

    // mutable Member ändern
    void increment() {
        ++value;
        cachedValue = value * 2; // mutable Member ändern
    }

    int getCachedValue() const {
        return cachedValue; // mutable Member lesen
    }

private:
    int value;                 // Normales Member, kann in einer const-Funktion nicht geändert werden
    mutable int cachedValue;   // Mutable Member, kann in const-Funktionen geändert werden
};

int main() {
    const Example ex;
    // ex.increment(); // Fehler: Auf einem const-Objekt kann keine nicht-const-Funktion aufgerufen werden
    // ex.value = 10; // Fehler: Member eines const-Objekts kann nicht geändert werden

    std::cout << "Value: " << ex.getValue() << std::endl;
    std::cout << "Cached Value: " << ex.getCachedValue() << std::endl; // Ausgabe ist 0

    return 0;
}

Anwendbare Szenarien:

  • Zwischenspeicherung: inconstBerechne und speichere das Ergebnis in der Funktion zwischen, ohne den externen Zustand des Objekts zu beeinflussen.
  • Zustandsverfolgung: Zum Beispiel Protokollzähler, Verfolgung von Informationen wie die Anzahl der Aufrufe, Vermeidung invasiver Änderungen an der Klassenlogik.

Notizen:

  • mutableBei der Verwendung der Variable ist Vorsicht geboten, um unerwartete Zustandsänderungen zu vermeiden, die die Lesbarkeit und Wartbarkeit des Codes beeinträchtigen können.
  • mutabletrifft auf die Notwendigkeit zu,constEs handelt sich um einen speziellen Fall der Zustandsänderung in der Umgebung, nicht um ein universelles Entwurfsmuster.

thread_local-Speicherklasse

thread_local ist eine in C++11 eingeführte Speicherklasse, die verwendet wird, um threadspezifische Variablen in einer Multithread-Umgebung zu verwalten.

Mit thread_local modifizierte Variablen haben unabhängige Instanzen in jedem Thread, sodass Operationen jedes Threads auf der Variable andere Threads nicht beeinflussen.

  • Unabhängigkeit: Jeder Thread hat seine eigene unabhängige Kopie der Variable, und Lese-/Schreiboperationen zwischen verschiedenen Threads beeinträchtigen sich nicht gegenseitig.
  • Lebenszyklus:thread_localDie Variable wird automatisch zerstört, wenn ihr Thread endet.
  • Initialisieren:thread_localVariablen können statisch oder dynamisch initialisiert werden und unterstützen die Initialisierung zum Zeitpunkt der Deklaration.

thread_local eignet sich für Szenarien, in denen Thread-Zustand, Caches gespeichert oder Datenrennen vermieden werden müssen, wie z. B. Thread-Pools, Anfragekontexte usw.

Das Folgende zeigt Variablen, die als thread_local deklariert werden können:

#include <iostream> #include <thread> thread_local int threadSpecificVar = 0; // Jeder Thread hat seine eigene threadSpecificVar void threadFunction(int id) { threadSpecificVar = id; // Thread-spezifische Variable setzen std::cout << "Thread " << id << ": threadSpecificVar = " << threadSpecificVar << std::endl; } int main() { std::thread t1(threadFunction, 1); std::thread t2(threadFunction, 2); t1.join(); t2.join(); return 0; }

Notizen:

  • Leistung: Da jeder Thread eine unabhängige Kopie hat,thread_localDie Zugriffsgeschwindigkeit auf die Variable kann etwas langsamer sein als bei globalen oder statischen Variablen.
  • Statischer Speicher:thread_localDer Speichertyp der Variablen ist die statische Speicherdauer, daher bleibt sie während der gesamten Laufzeit des Programms bestehen.
Weitere Erweiterungen