C++ Core Guidelines: The Standard Library
The rules to the C++ standard library are mainly about containers, strings, and iostreams.
Curiously, there is no section on the standard template library (STL) algorithms in this chapter. Curiously, because there is a proverb in the C++ community: If you write an explicit loop, you don’t know the algorithms of the STL. Anyway. Only for completeness, let me start with the first three rules which do provide not much beef.
SL.1: Use libraries wherever possible, because reinventing the wheel is a bad idea. Additionally, you benefit from the work of others. This means you use already tested and well-defined functionality. This holds, in particular, true if you SL.2: Prefer the standard library to other libraries. Imagine, for example, you hire someone. The benefit is that he already knows the library, and you don’t have to teach him your libraries. You save a lot of money and time. I once had a customer who named his infrastructure namespace std. Of course, if you want to have a lot of fun, do it. If not: SL.3: Do not add non-standard entities to namespace std
.
The next rules to STL containers are more concrete.
Containers
The first rule is quite easy to argue.
SL.con.1: Prefer using STL array
or vector
instead of a C array
I assume you know a std::vector. One of the significant advantages of a std::vector to a C array is that the std::vector automatically manages its memory. Of course, that holds true for all further containers of the Standard Template Library. But now, let’s look closer at the automatic memory management of std::vector.
std::vector
// vectorMemory.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::vector<int> vec; // (1) std::cout << "Maximal size: " << std::endl; std::cout << "vec.max_size(): " << vec.max_size() << std::endl; // (2) std::cout << std::endl; std::cout << "Empty vector: " << std::endl; showInfo(vec, "Vector"); std::cout << std::endl; std::cout << "Initialised with five values: " << std::endl; vec = {1,2,3,4,5}; showInfo(vec, "Vector"); // (3) std::cout << std::endl; std::cout << "Added four additional values: " << std::endl; vec.insert(vec.end(),{6,7,8,9}); showInfo(vec,"Vector"); // (4) std::cout << std::endl; std::cout << "Resized to 30 values: " << std::endl; vec.resize(30); showInfo(vec,"Vector"); // (5) std::cout << std::endl; std::cout << "Reserved space for at least 1000 values: " << std::endl; vec.reserve(1000); showInfo(vec,"Vector"); // (6) std::cout << std::endl; std::cout << "Shrinke to the current size: " << std::endl; vec.shrink_to_fit(); // (7) showInfo(vec,"Vector"); }
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
To spare typing, I wrote the small function showInfo. This function returns for a vector its size and its capacity. The size of a vector is its number of elements, and the capacity of a container is the number of elements a vector can hold without an additional memory allocation. Therefore, the capacity of a vector has at least to be as big as its size. You can adjust the size of a vector with its method resize and the capacity of a container with its method reserve.
But, back to the program from top to bottom. I create (line 1) an empty vector. Afterward, the program displays (line 2) the maximum number of elements a vector can have. After each operation, I return their size and capacity. That holds for the initialization of the vector (line 3), for the addition of four new elements (line 4), the resizing of the containers to 30 elements (line 5), and the reserving of additional memory for at least 1000 elements (line 6). With C++11, you can shrink with the method shrink_to_fit (line 7) the vector’s capacity to its size.
Before I present the program’s output on Linux, let me make a few remarks.
- Adjusting the size and capacity of the container is done automatically. I haven’t used any memory operations like new and dele
- By using the method vec.resize(n) the vector vec will get new default-initialized elements if n > cont.size() holds.
- By using the method vec.reserve(n) the container vec will get new memory for at least n elements if n > cont.capacity() holds.
- The call shrink_to_fit is non-binding. That means the C++ runtime has not had to adjust the capacity of a container to its size. But, using the method shrink_to_fit with GCC, clang, or cl.exe always freed the unnecessary memory.
Okay, but what is the difference between a C array and a C++ array?
std::array
std::array combines the best of two worlds. On the one hand, std::array has the size and efficiency of a C-array; on the other hand, std::array has the interface of a std::vector.
My small program compares the memory efficiency of a C array, a C++ array (std::array), and a std::vector.
// sizeof.cpp #include <iostream> #include <array> #include <vector> int main(){ std::cout << std::endl; std::cout << "sizeof(int)= " << sizeof(int) << std::endl; std::cout << std::endl; int cArr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; std::array<int, 10> cppArr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; std::vector<int> cppVec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; std::cout << "sizeof(cArr)= " << sizeof(cArr) << std::endl; // (1) std::cout << "sizeof(cppArr)= " << sizeof(cppArr) << std::endl; // (2) // (3) std::cout << "sizeof(cppVec) = " << sizeof(cppVec) + sizeof(int) * cppVec.capacity() << std::endl; std::cout << " = sizeof(cppVec): " << sizeof(cppVec) << std::endl; std::cout << " + sizeof(int)* cppVec.capacity(): " << sizeof(int)* cppVec.capacity() << std::endl; std::cout << std::endl; }
The C array (line 1) and the C++ array (line 2) take 40 bytes. That is precisely sizeof(int) * 10. In contrast, the std::vector needs additional 24 bytes (line 3) to manage its data on the heap.
This was the C part of a std::array, but the std::array supports the interface of a std::vector. This means, in particular, that std::array knows its size; therefore, error-prone interfaces such as the following one are a heavy code smell.
void bad(int* p, int count){ ... } int myArray[100] = {0}; bad(myArray, 100); // ----------------------------- void good(std::array<int, 10> arr){ ... } std::array<int, 100> myArray = {0}; good(myArray);
When you use a C array as a function argument, you remove almost all type information and pass it as a pointer to its first argument. This is extremely error-prone because you must also provide the number of elements. If your function accepts a std::array<int, 100>, this will not hold.
You can use a template if the function good is not generic enough.
template <typename T> void foo(T& arr){ arr.size(); // (1) } std::array<int, 100> arr{}; foo(arr); std::array<double, 20> arr2{}; foo(arr2);
Because a std::array knows its size, you can ask for it in line 1.
What’s next?
The following two rules for containers are pretty interesting. In the next post, I will give an answer to the question: When to use which container?
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!