Automatic Memory Management of the STL Containers
One of the significant advantages of a C++ string to a C string and of a std::vector to a C array is that both C++ containers automatically manage their memory. Of course, that holds for all further containers of the Standard Template Library. In this post, I will look closer at the automatic memory management of std::vector and std::string.
From the user’s perspective, a std::string in C++11 feels like a std::vector. That is the simple reason I can present them in parallel. Therefore, it fits very well that std::string and std::vector are the essential containers in C++.
std::string and std::vector
The C++ runtime automatically adjusts the size of a std::string and std::vector to its number of elements. And with C++11 in both directions.
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; } |
The program is relatively easy to get. That was my first thought. But wait for a second.
To spare typing, I wrote the small function showInfo (lines 7 – 13). This function returns for a container its size (line 10) and capacity (line 11). The size of a container is its number of elements, and the capacity of a container is the number of elements a container can hold without an additional allocation. Therefore, the capacity of the container has at least to be as big as its size. You can adjust the size of a container with its method resize (lines 49 and 50); you can adjust the capacity of a container with its method reserve (lines 56 and 57).
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
But, back to the program from top to bottom. I create in lines 19 and 20 an empty string and an empty vector. Afterward, the program displays the maximum number of elements a string or vector can have. After each operation on both containers, it returns their size and capacity. That holds for the initialization of the containers (lines 34 and 35), for the addition of four new elements (lines 42 and 43), the resizing of the containers to 30 elements (lines 49 and 50), and the reserving of additional memory for at least 1000 elements (line 56 and 57). With C++11, you can shrink the container’s capacity to its size with the method shrink_to_fit (lines 63 and 64).
Before I present the program’s output on Linux and Windows, let me make a few observations.
- Adjusting the size and capacity of the container is done automatically. I haven’t used any memory operations like new and delete.
- std::string and std::vector support the same interface. The only exception to this rule is line 41. Here I added a C string to a C++ string.
- By using the method cont.resize(n), the container cont will get new default-initialized elements if n > cont.size() is accurate.
- By using the method cont.reserve(n), the container cont will be get new memory for at least n elements if n > cont.capacity() is true.
- The call shrink_to_fit is non-binding. That means the C++ runtime has not have to adjust the capacity of a container to its size. But, my usages of the method shrink_to_fit with GCC, clang, or cl.exe always freed the unnecessary memory.
Here is the output of the program.
My little astonishment
The program shows that the MSVC 15 STL implementation is slightly more greedy than the GCC 4.8 STL implementation. That holds, in particular, true for std::string. Therefore, the empty std::string has 15 elements. But I was more astonished that the maximum size of a std::string is as big as the maximum size of a std::vector<int> on Linux. This is astonishing because an int is four times as big as a char on Linux and 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; }
You have to interpret both values as maximum values. Any ideas on the discrepancy?
My astonishment has disappeared
Thanks to the help of Mark Abraham and Clément Gregoire, the riddle is solved.
Microsoft implementation is more greedy
Microsoft Visuals std::string implementation uses internally small string optimization. Therefore, a small string needs no heap allocation and goes directly to the stack. The boundary is exactly 15 characters. GCC will get a conformant string implementation with 5.1. But I used GCC 4.8 in my test.
GCC with conformant std::string implementation
If I use GCC 5.3 with a conformant std::string implementation, the picture will differ.
To use the conformant std::string implementation in GCC 5.3, I must use the compiler flag
What’s next?
I will have in the next post a closer look at std::array. std::array combines the best of two worlds. On the one hand, std::array is as lightweight as a C array; on the other hand, std::array supports the same interface as a std::vector.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!