About the C++ Core Guidelines

About the C++ Core Guidelines

By Andreas Fertig

Overload, 25(140):25-27, August 2017


The C++ core guidelines are a useful summary of C++ best practice. Andreas Fertig shows their use.

In 2015 at CppCon, Bjarne Stroustrup announced the C++ Core Guidelines [ CCG ] in his opening keynote. These guidelines are a summary of best practices intended to overcome known traps and pitfalls of the C++ programming language. All the rules are grouped into several sections ranging from philosophy, interfaces and classes to performance and concurrency. The project is designed as an open source project, which should ensure that it will improve over time along with the language itself.

Behind this set of rules are some different ideas because, as Bjarne mentioned in his talk: “ We all hate coding rules ” [ Sommerlad16 ]. Often, the rules are written by people with weak experience with the particular programming language. They tend to keep the use of more complex features to a minimum, with the aim of preventing misuse by less experienced programmers. Both concepts are invalid for the Core Guidelines, which are written by experts in the field targeting programmers with C++ experience. The aim of the Core Guidelines is to assist people in using C++ effectively, which implies transitioning legacy C++ code towards modern C++, using C++11 or newer. The guidelines also focus on the language itself and using its power. For example, enable the use of static code analysis by expressing your intent in the language while leaving comments only for documentation. Consider this example:

  int i = 0;
  while( i < v.size() )
  {
    // do something with v[i]
  }

By using modern C+, this can be transformed into something like this:

  for(auto const& x: v)
  {
    // do something with x
  }

(Example taken from [ Sommerlad16 ].)

There is a big difference between these two code fragments. In the first, a new variable comes into scope, and several problems can occur if it is reused. For example, it can lead to an out of bounds access Furthermore, there is the potential of getting the array access of v wrong by adding +1 to the loop variable. Tampering with this variable in other ways is also possible. These are typical mistakes, nothing somebody will do wrong on purpose. In the modern version, there is no need for an additional variable. It is also clear that the author is only interested in the objects of a vector. No modification will take place, hence the const reference. Since we are interested in all elements in the vector for the loop this is clearest. Last but not least, the modern version is much clearer to the compiler and static analysis tools. The compiler for instance, will not allow compilation if a write access to x takes place. A static analysis tool can understand that this is a way of iterating over the whole set of vector elements. You can improve it even more by using functions from std::algorithm like std::replace or std::find .

When using the C++ Core Guidelines, C++ becomes a little different. In Bjarne’s words: “ Within C++ is a smaller, simpler, safer language struggling to get out ” [ Stroustrup15 ]. This means that all the rules in the Core Guidelines work with a modern C++ compiler 1 . No additional extensions are required, albeit there are assisting libraries to facilitate using the rules. Let’s look at some of the rules.

Signalling failure

There is:

I.10: Use exceptions to signal a failure to perform a required task.

That’s a rule I struggle with. The standard library uses exceptions as the main failure signalling mechanism. It fully denotes the word ‘exception’. We do not expect such an event, so it’s reasonable to throw an exception at this point. This also leaves the return value for returning a value in case the function was successful. My struggle here is, if nobody expects something why should anyone catch it? A ball thrown at you unexpectedly can hurt a lot because you were not ready to catch it. Well, in case of C++, it’s like you are fast enough to duck. Then, at least you do not get hurt, but what about the others? You can let an exception which was caused by a function your code called pass to whoever called you. Now, the next higher function in the call stack has to deal with it. This pattern can continue until we hit main . Then, the program will terminate. Let’s say somebody within the call stack does catch the exception – now what? There is often no good choice. In the layers above, nobody knows which call triggered the exception and how to react to it. A horrible scenario for embedded systems which are somewhat critical! There may be millions of lines of code out there which can throw an exception, but I prefer not to. I use my freedom to not pick this rule for me.

What can we do instead? A solution I have come across multiple times is the following

  int SomeFunction(int param1, double param2,
    int* outValue)
  {
    //...
  }

Let us suppose the returned value uses the full range of its data type; then, there is no space left to squeeze in the error code. Now, the choice is either to return the error code and pass the actual return value into the function as a pointer or reference parameter, or vice versa. Both are suboptimal.

The guidelines provide an alternative in section I.10: “ using a style that returns a pair of values ”.

  auto [val, error_code] = do_something();
  if (error_code == 0)
  {
    // ... handle the error condition
  }

  // ... use val

It uses structured bindings which are available in C++17. This allows us to return a struct and directly assign variables to the members. The resulting code is much clearer and robust compared to the variant shown before.

However, there is another alternative: std::optional (see Listing 1).

std::optional<std::string> GetUserName(int uid)
{
  if( uid == 0 )
  {
    return "root";
  }
  return {};
}

void UsingOptional()
{
  if( auto str = GetUserName(0) )
  {
    std::cout << *str << "\n";
  }

  auto fail = GetUserName(1);
  std::cout << fail.value_or("unknown") << "\n";
}
			
Listing 1

We can ask the optional object whether or not it contains a value, meaning it can be used in a boolean expression. In case you would like to skip all those checks, you can invoke it with value_or() and pass a value which is used when the object does not contain a valid object. Pretty neat.

Safe and modern array passing

Let’s move on to another item:

I.13: Do not pass an array as a single pointer.

This aims to solve a popular problem we can often see in the wild. For example, in the safe version of string copy:

  char* strncpy(char* dst, const char* src,
    size_t n)
  {
    // ...
  }

Wow, how safe is that? We have a single size_t parameter. To which value does it apply? Alright, it enables us to write code like this:

  strncpy( dst, src, MIN(dstSize, srcSize) );

Honestly, does this code look good to you? Writing MIN() over and over again? How many mistakes can still be made? Rule I.13 is about getting rid of code like this. Instead, there is the template class span which uses the power of templates to deduce the size of the object. You can also cut it down to just a slice of the array. The resulting object can be queried for its size, hence the chances for discrepancies are reduced by a lot. It is one object containing data and size. An improved string copy function would look like this:

  span<char> strcpy(span<char> dst,
             span<const char> src)
  {
    // ...
  }

If you pay close attention, you will notice that we no longer need to check for null pointers in strcpy .

No raw pointers

Another rule is

R.10: Avoid malloc() and free() .

Together with

R.11: Avoid calling new and delete explicitly

it aims to reduce the use of uncontrolled memory allocation, with the goal of preventing memory leaks. In C++ with objects, malloc and free do nothing good for us. They are legacies from C. The guideline tells us to avoid new and delete in their naked form as well. In modern C++, the use of so called raw pointers, pointers without an owner, are discouraged. To handle resource management better, allocated memory should belong to an owner: some object which takes care of the lifetime of the memory. In modern C++, we have several kinds of managing pointers: unique_ptr , shared_ptr and weak_ptr . Helper functions like make_unique are available to assist us create such a pointer without writing new ourselves. Afterwards, the smart pointers take care of the allocated owned memory.

In case none of those managing pointers matches your needs, fallback to owner<T> . The idea behind it is to state the ownership of a simple pointer. In a perfect world, all owner<T> instances would be a managing a pointer like unique_ptr . When we are not there, owner<T> can be helpful for static analysis. Pointers which are not owning must not free memory. On the other hand, owning functions must free memory as soon as they go out of scope.

A library for the guidelines

For the best support of the C++ Core Guidelines, there is a library called ‘Guideline Support Library’ (GSL). Microsoft provides an implementation of it under the MIT licence hosted in github [ Microsoft ]. The concept of the library is to provide ready-to-use functions which enforce the idea of the C++ Core Guidelines, and increase the safety and correctness of a program.

There are simple things in it like at() . This tiny template function provides a bound-checked way of accessing built-in arrays like char buffer[1024].There are also places for things we did wrong for a long time: narrow_cast ; again a template function which mimics the style of a C++ cast. Under the hood it checks whether the narrowing will lose signedness or results in a different value. Many of the checks are run-time checks. However, it is a way of letting static analysers know what you intend to do, and in doing so, there is a chance finding bugs before run-time.

In many ways, the GSL is similar to the boost [ Boost ] library. For years, boost has driven some new ideas and language improvements by letting the community try it out and decide if an idea is useful, all without compiler or standards changes. Some improvements of boost have found their way into recent C++ standards. The GSL may do the same for the community. In fact, they managed to get the first item of the GSL into the shiny new C++ standard C++17: std::byte [ C++ ].

Summary

In summary, the C++ Core Guidelines try to encourage using modern C++. There is word on the street that they contain too many rules which at some point overlap. Still, they are a comprehensive collection of possible mistakes which can be avoided. Consider looking at the C++ Core Guidelines for ideas of how to write modern C++ and, of course, pick the items you consider valuable for your project. Also have a look at the GSL (multiple implementations are available) as it helps you write safer and more robust code.

Acknowledgements

Thanks to Peter Sommerlad who reviewed draft versions of this article. His comments contributed to substantial improvements to the article.

Artwork by Franziska Panter from panther concepts, https://panther-concepts.de

References

[Boost] http://www.boost.org/

[C++] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0298r2.pdf

[CCG] https://github.com/isocpp/CppCoreGuidelines

[Microsoft] https://github.com/Microsoft/GSL

[Sommerlad16] http://wiki.hsr.ch/PeterSommerlad/files/ESE2016_core_guidelines.pdf

[Stroustrup15] https://github.com/CppCon/CppCon2015/blob/master/Keynotes/Writing%20Good%20C%2B%2B14/Writing%20Good%20C%2B%2B14%20-%20Bjarne%20Stroustrup%20-%20CppCon%202015.pdf

  • There is an exception when it comes to guidelines involving concepts.





Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.