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 arrays 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. At 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 such 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, 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, Peter Ware, Tobi Heideman, Daniel Hufschläger, Red Trip, Alexander Schwarz, and Tornike Porchxidze.

 

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

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

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

English

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++,

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

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 4641

Yesterday 7909

Week 12550

Month 87601

All 6108665

Currently are 217 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments