Automatisches Speichermanagement mit Containern

Einer der großen Vorteile des C++-Strings gegenüber dem C-String, bzw. dem std:::vector gegenüber dem C-Array ist es, dass diese C++ Datenstrukturen automatisch ihren Speicher verwalten. Dies trifft natürlich neben dem std::string auf alle Container der Standard Template Library zu. Diese automatische Speicherverwaltung will ich mir in diesem Artikel für den std::vector und den std::string genauer anschauen.

 

Von der Perspektive eines Anwenders betrachtet, verhält sich ein std::string insbesondere mit C++11 sehr ähnlich wie ein std::vector. Das ist der einfache Grund, warum ich beide Datenstrukturen gleichzeitig vorstellen kann. Da trifft es sich gut, dass std::string und std::vector die wichtigsten Container in C++ sind.

std::string und std::vector

Die C++-Laufzeit passt die Größe des std::string und des std::vector automatisch an die Anzahl seiner Elemente an. Und das mit C++11 nicht nur in eine Richtung.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// stringAndVector.cpp

#include <iostream>
#include <string>
#include <vector>

template <typename T>
void showInfo(const T& t,const std::string& name){

  std::cout << name << " t.size(): " << t.size() << std::endl;
  std::cout << name << " t.capacity(): " << t.capacity() << std::endl;

}

int main(){
    
  std::cout << std::endl;

  std::string str;
  std::vector<int> vec;
  
  std::cout << "Maximal size: " << std::endl;
  std::cout << "str.max_size(): " << str.max_size() << std::endl;
  std::cout << "vec.max_size(): " << vec.max_size() << std::endl;
  std::cout << std::endl;
  
  std::cout << "Empty string and vector: " << std::endl;
  showInfo(str,"String");
  showInfo(vec,"Vector");
  std::cout << std::endl;
  
  
  std::cout << "Initialized with five values: " << std::endl;
  str= {"12345"};
  vec= {1,2,3,4,5};
  showInfo(str,"String");
  showInfo(vec,"Vector");
  std::cout << std::endl;
  
  std::cout << "Added four additional values: " << std::endl;
  str += "6789";
  vec.insert(vec.end(),{6,7,8,9});
  showInfo(str,"String");
  showInfo(vec,"Vector");
  std::cout << std::endl;
  
  
  std::cout << "Resized to 30 values: " << std::endl;
  str.resize(30);
  vec.resize(30);
  showInfo(str,"String");
  showInfo(vec,"Vector");
  std::cout << std::endl;

  std::cout << "Reserved space for at least 1000 values: " << std::endl;
  str.reserve(1000);
  vec.reserve(1000);
  showInfo(str,"String");
  showInfo(vec,"Vector");
  std::cout << std::endl;
  
  std::cout << "Shrinked to the current size: " << std::endl;
  str.shrink_to_fit();
  vec.shrink_to_fit();
  showInfo(str,"String");
  showInfo(vec,"Vector");
  std::cout << std::endl;

}

 

Das Programm ist fast selbsterklärend. Dachte ich zu mindestens. Dazu aber gleich mehr.

Um Tipparbeit zu sparen, habe ich mir die kleine Funktion showInfo (Zeile 7 - 13) definiert, die mir für einen konkreten Container dessen Größe (Zeile 10) und Kapazität (Zeile 11) ausgibt. Die Größe eines Containers ist die Anzahl seiner Elemente, die Kapazität eines Containers ist die Anzahl der Elemente, die er maximal besitzen kann, ohne dass eine Reallokation stattfinden muss. Daher gilt natürlich, dass die Kapazität eines Containers immer mindestens so groß ist wie seine Größe. Während die Größe eines Containers durch die Methode resize (Zeile 49 und 50) vergrößert wird, lässt sich seine Kapazität mit der Methode reserve (Zeile 56 und 57) anpassen.

Nun aber das Programm von Anfang bis zum Ende. In Zeile 19 und 20 erzeuge ich einen leeren String und einen Vector. Dann stelle ich die maximale Anzahl der Elemente dar, die ein String bzw. Vektor besitzen kann. Nach jeder weiteren Operationen auf dem std::string und dem std::vector gebe ich dessen Größe und Kapazität aus. Das trifft für die Initialisierung der Container (Zeile 34 und 35), für das Hinzufügen von vier Elementen (Zeile 42 und 43) zum Container, das Vergrößern der Container auf 30 Elemente (Zeile 49 und 50) und das Reservieren von zusätzlichem Speicherplatz für 1000 Elemente (Zeile 56 und 57) zu. Selbst das Verkleinern des Containers auf seine tatsächliche Größe wird mit der Methode shrink_to_fit (Zeile 63 und 64) in C++11 unterstützt. 

Bevor ich die Ausgabe des Programms auf Linux und Windows vorstelle, möchte ich meine wichtigsten Beobachtungen zusammenfassen.

  1. Die Anpassung der Größe und Kapazität der Container geschieht automatisch. Ich verwende in diesem Programm keine expliziten Aufrufe zur Speicherallokation oder -deallokation.
  2. std::string und std::vector unterstützen das gleiche Interface bei meinen Operationen. Lediglich in Zeile 41 habe ich einen C-String zum bestehenden C++-String hinzu addiert.
  3. Durch die Methodenausführung cont.resize(n) erhält der Container cont neue default-initialisierte Elemente, wenn n > cont.size() ist.
  4. Durch die Methodenausführung cont.reserve(n) wird neuer Speicher für mindestens n Elemente für cont reserviert, wenn  n > const.capacity() ist.
  5. Der Aufruf der Methode shrink_to_fit ist nicht bindend. Das heißt, die C++-Laufzeit muss die Kapazität des Containers nicht an seine Größe anpassen. Bei all meinen Anwendungen von shrink_to_fit mit GCC, clang oder cl.exe wurde der unnötige Speicher ab immer freigegeben.

Nun aber zur Ausgabe des Programms.

 

stringAndVectorstringAndVectorWin

Meine kleine Verwunderung

Schön zeigt das Programm, dass der cl.exe Compiler unter Windows ein bisschen gieriger ist. Dies trifft insbesondere auf den std::string zu. So besitzt der leere std::string Speicher für 15 Elemente. Mehr hat mich aber gewundert, dass unter Windows die maximale Größe eines std::string um den Faktor 4 größer ist als die eines std::vector<int>. Dieser Faktor 4 gilt auch zwischen einem std::string unter Windows und Linux. Dies gilt, obwohl die Datentypen char und int gleich groß sind.

Aber ich war mehr über die Tatsache verwundert, dass die maximale Größe eines std::string unter Linux die gleiche ist die maximale Größe eines std::vector<int>. Das ist verwunderlich, ist doch int viermal so groß wie char unter Linux und Windows.

#include <iostream>

int main(){
    
  std::cout << std::endl;

  std::cout << "sizeof(char): " << sizeof(char) << std::endl;
  std::cout << "sizeof(int): " << sizeof(int) << std::endl;
  
  std::cout << std::endl;

}

 

sizeofsizeofWin

 

Diese Werte lassen sich nur als Maximalwerte interpretieren. 

Wie geht's weiter?

Im nächsten Artikel stelle ich std::array genauer vor. std::array vereint das beste aus zwei Welten. Zum einen ist er so leichtgewichtig wie das C-Array, zum anderen bietet er das Interface eines std::vector an.

 

 

 

 

 

 

 

 

 

Mentoring

Stay Informed about my Mentoring

 

Rezensionen

Tutorial

Besucher

Heute 97

Gestern 1288

Woche 7800

Monat 8900

Insgesamt 3918062

Aktuell sind 54 Gäste und keine Mitglieder online

Kubik-Rubik Joomla! Extensions

Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare