C++ Core Guidelines: The Standard Library

Contents[Show]

The rules to the C++ standard library are mainly about containers, strings, and iostreams.

iso 154533 1280

Curiously, there is no section to the algorithms of the standard template library (STL) 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 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 big advantages of a std::vector to a C array is it that the std::vector automatically manage its memory. Of course, that holds true for all further containers of the Standard Template Library. But now, let's have a closer look 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");

}

 

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, 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; you can adjust 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. Afterwards, the program displays (line 2) the maximum numbers 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 output of the program on Linux let me make a few remarks.

  1. 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 dele
  2. By using the method vec.resize(n) the vector vec will get new default-initialized elements, if n > cont.size() holds.
  3. By using the method vec.reserve(n) the container vec will get new memory for at least n elements, if n > cont.capacity() holds.
  4. 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.

vectorMemory

Okay, but what is the difference between a C array and a C++ array?

std::array

std::array combines the best from two worlds. On 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;
  
}

 

sizeof

Both, the C array (line1) and the C++ array (line 2) take 40 bytes. That is exactly 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 and, 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 have to provide the number of elements additionally. This will not hold if your function accepts an std::array<int, 100>.

If the function good is not generic enough, you can use a template.

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 next two rules to containers are quite interesting. In the next post, I give an answer to the question: When to use which container?

 

 

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, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Louis St-Amour, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Tobi Heideman, Daniel Hufschläger, Red Trip, Alexander Schwarz, Tornike Porchxidze, Alessandro Pezzato, Evangelos Denaxas, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Dimitrov Tsvetomir, Leo Goodstadt, Eduardo Velasquez, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, and Michael Young.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, and Rusty Fleming.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++,

RainerGrimmSmall

My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 1345

Yesterday 8162

Week 18215

Month 136417

All 7404257

Currently are 142 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments