One of the big advantages of C++ string to a C string and of a std::vector to a C arrays is it that both C++ containers automatically manage their memory. Of course, that holds true for all further containers of the Standard Template Library. In this post, I will have a closer look at the automatic memory management of std::vector and std::string.
From the user 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 most important 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 quite easy to get. That was my first thought. But wait a second.
To spare typing I wrote the small function showInfo (line 7 - 13). This function returns for a container its size (line 10) and its capacity (line 11). The size of a container is its number of elements, the capacity of a container is the number of elements a container can hold without an additional allocation. Therefore, the capacity of container has at least to be as big as its size. You can adjust the size of a container with its method resize (line 49 and 50); you can adjust the capacity of a container with its method reserve (line 56 and 57).
But, back to the program from top to bottom. I create in line 19 and 20 an empty string and an empty vector. Afterwards, the program displays the maximum numbers of elements a string or vector can have. After each operation on the both container it returns their size and capacity. That holds true for the initialization of the containers (line 34 and 35), for the addition of four new elements (line 42 and 43), the resizing of the containers to 30 elements (line 49 and 50) and the reserving of additional memory for at least 1000 elements (line 56 and 57). With C++11, you can shrink with the method shrink_to_fit (line 63 and 64) the container's capacity to its size.
Before I present the output of the program on Linux and Windows let me make a few observations.
- The adjustment of the size and the capacity of the container is done automatically. I haven't used any kind of 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 true.
- 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 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 a little bit 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 by the fact 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 a 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 to 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.
If I use a GCC 5.3 with a conformant std::string implementation, the picture will be different.

To use the conformant std::string implementation in GCC 5.3, I have to use the compiler flag -D_GLIBCXX_USE_CXX11_ABI=1.Now, the maximum size of std::string is two times the maximum size of std::vector<int>. The C++11 standard says about max_size(): "The size of the largest possible string." Now, I'm fine.
What's next?
I will have in the next post a closer look at std::array. std::array combines the best from two worlds. On 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, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Darshan Mody, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Wolfgang Gärtner, Louis St-Amour, Stephan Roslen, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Avi Kohn, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, and Peter Ware.
Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, and Embarcadero Marketing.
Seminars
I'm happy to give online-seminars or face-to-face seminars world-wide. Please call me if you have any questions.
Bookable (Online)
Deutsch
Standard Seminars
Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.
New
Contact Me
Modernes C++,

Comments
Pete, you are right. My reasoning is totally broken. I will adjust it.
I was searching on Google for something else, Anyways I am here now and would just like to say
many thanks for a tremendous post and a all round enjoyable blog (I also love the theme/design), I don’t have time to browse
it all at the moment but I have bookmarked it and also added your RSS feeds,
so when I have time I will be back to read much more, Please do keep up
the great jo.
RSS feed for comments to this post