How to avoid bugs using modern C++

What is Modern C++?

The term Modern C++ became very popular after the release of C++11. What does it mean? First of all, Modern C++ is a set of patterns and idioms that are designed to eliminate the downsides of good old “C with classes”, that so many C++ programmers are used to, especially if they started programming in C. C++11 looks way more concise and understandable, which is very important.

What do people usually think of when they speak about Modern C++? Parallelism, compile-time calculation, RAII, lambdas, ranges, concepts, modules, and other equally important components of the standard library (for example, an API to work with the file system). These are all very cool modernizations, and we are looking forward to seeing them in the next set of standards. However, I would like to draw attention to the way the new standards allow writing more secure code. When developing a static analyzer, we see a great number of varying errors, and sometimes we cannot help thinking: “But in modern C++ this could have been avoided”. Therefore, I suggest we examine several errors that were found by PVS-Studio in various Open Source projects. Also, we’ll see how they can be fixed.

Automatic type inference

In C++, the keywords auto and decltype were added. Of course, you already know how they work.

std::map<int, int> m;
auto it = m.find(42);
//C++98: std::map<int, int>::iterator it = m.find(42);

It’s very convenient to shorten long types, without losing the readability of the code. However, these keywords become quite expansive, together with templates: there is no need to specify the type of the returning value with auto and decltype.

But let’s go back to our topic. Here is an example of a 64-bit error:

string str = .....;
unsigned n = str.find("ABC");
if (n != string::npos)

In a 64-bit application, the value of string::npos is greater than the maximum value of UINT_MAX, which can be represented by a variable of unsigned type. It could seem that this is a case where auto can save us from this kind of problem: the type of the n variable isn’t important to us, the main thing is that it can accommodate all possible values of string::find. And indeed, if we rewrite this example with auto, the error is gone:

string str = .....;
auto n = str.find("ABC");
if (n != string::npos)

But not everything is as simple. Using auto is not a panacea, and there are many pitfalls related to its use. For example, you can write the code like this:

auto n = 1024 * 1024 * 1024 * 5;
char* buf = new char[n];

Auto won’t save us from the integer overflow and there will be less memory allocated for the buffer than 5GiB.

Auto also isn’t of any great help when it comes to a very common error: an incorrectly written loop. Let’s look at an example:

std::vectorlt;intgt; bigVector;
for (unsigned i = 0; i lt; bigVector.size(); ++i)
{ ... }

For large size arrays, this loop becomes an infinity loop. It’s no surprise that there are such errors in the code: they reveal themselves in very rare cases, for which there were no tests.

Can we rewrite this fragment with auto?

std::vector<int> bigVector;
for (auto i = 0; i < bigVector.size(); ++i)
{ ... }

No. Not only is the error is still here. It has become even worse.

With simple types auto behaves very badly. Yes, in the simplest cases (auto x = y) it works, but as soon as there are additional constructions, the behavior can become more unpredictable. What’s worse, the error will be more difficult to notice, because the types of variables aren’t that obvious at first glance. Fortunately it is not a problem for static analyzers: they don’t get tired, and don’t lose attention. But for us, as simple mortals it’s better to specify the types explicitly. We can also get rid of the narrowing casting using other methods, but we’ll speak about that later.

More read here:

This article was contribute. (UGC)

More by Author

Must Read