10 ways to not shoot yourself in the foot using C++11

Within C++, there is a much smaller and cleaner language struggling to get out (Stroustrup)

The following text could be modified – current rev.1.0

Very often i hear from java/erlang/etc people that C++ is so bad that it is very unwise to use so-old-school language now, when we have more ‘safe’ higher level languages. Everybody heard about foot-shooting using C++.  What about C++11?

Bjarne said that C++11 feels like whole new language and, at first, i did not take it seriously as modifications looked more as minor additions (especially for boost users). Now i changed my mind – using new features combined together can transform your way of coding into new form. I’m talking not about adding new features to your code, but about changing your coding style.

How not to shoot yourself in the foot? Here is the list of my rules to make the C++ coding life sweet and easy. This is simple convention to follow and can be adopted very fast. Not only it gives more stable implementation but also more clean and understandable design.

This convention is composition of Scott Meyers rules, Functional programming ideas and reducing-complexity ideology by Steve McConnell.

This convention adds minor overhead to implementation so when you really need to optimise minor parts of your code you can skip some rules. Also there are some cases when you can skip the rules as they will produce less maintainable solutions (see descriptions).

Some rules have strong form – more profit and a bit more overhead. And if you follow strong forms C++ will truly feel like a different language.

All this is discussable as its only my own solution and can be corrected and improved. You are welcome to add some comments below.

RULES

Rule 1 – Use RAII and smart pointers only! (Strong: use only std::shared_ptr and RAII) 

Memory management is one of most referenced problems of C++ – working with raw pointers is like dancing with blades. To solve this automatically we have two approaches: GC from java/C# or Objective-C’s reference counting. I prefer reference counting as more controllable solution – so use smart pointers everywhere (or no pointers at all using RAII).

Strong version: use shared_ptr for every pointer to prevent mistakes with passing of ownership. This makes additional copy overhead but this is the prices for safety. Only when you need to optimise some bottlenecks you can bypass them.

You have to keep shared pointer specific in mind. First problem with reference counting is situation when there is ownership cycle between two classes. This situation could be reproduced inside special data structures only and should not happen at normal conditions. Typically if you have such pointer ring this is indication of bad design. Second problem is how not to keep some data as zombie. To avoid this you just need not to store pointers to data inside long living objects (as cache or something). So don’t treat smart pointers as some magic – logic for shared_ptr is quite simple.

Last thing – remember that in multithread environment only composition of immutable data/thread-safe data and shared pointers can lead to safe implementation.

Rule 2 – Use const where its possible (Strong: use immutable data only)

Making things const can reduce entropy inside your code. If method requires input parameter as const – mark it so. If method does not affect class – mark it const. If class field can be marked const – do so. This not only gives compiler more space for optimisations, but reduces the chance to make some unwanted modifications to zero.

Common mistakes when working with modified object state can be prevented using approach from functional programming – make whole your data immutable. This even solves problems with concurrent access from different threads.

By immutable data class i mean class with const fields (which are inited during creation) and no default empty constructor.

Such declaration can be generated by macro which can contain copy constructors, serializators/deserializators, etc. Note that move semantics is not applicable to immutable structures as move modifies source object and immutable object can’t be modified.

You can have shared_ptr as container for your immutable data to save space and resources while passing data through processing chain. You can even wrap all business classes into shared pointers and accept convention that your data classes are wrapped by default.

If you are familiar with convention where you put Ptr at end of smart pointer class names to make codes understand that its pointer, you would argue here that this will reduce readability. But actually if you do it for whole business domain it will on the contrary make thing more compact and understandable when you work with functional processing.

There are some cases where some algorithms can be expressed in a lot more compact way when some variable inside is mutable. Even if you decided to go that way for readability or speed make sure to cover this part with tests with additional caution.

Rule 3 – Use make_shared() instead of ‘new‘ for shared pointers

To get rid of new/delete notation completely use make_shared for smart pointers. About benefits of make_shared relating to memory allocation you can read in documentation.

As make_shared requires class type as template parameter one can add some sugar as optional step:

And init objects like:

Note that you should have no destruction functions.

Rule 4 – Use for(const auto& x :X) instead of old school cycles (Strong: use indexed cycles in simple pure functions only)

Instead of old indexed ‘for’ cycles use C++11 foreach approach: for(const auto& x :X). When you have no explicit cycle index you can make no range mistakes. And this notation has more readable form. Instead of auto you can use class name if this is needed for improving readability. Also note optional keyword const here and, of course, you can skip & sign here. For more details read Scott Meyers.

Your custom collections can be extended to support for(:) with ease:

Strong version: all algorithms which require some indexed cycles should be implemented as low level functions and should be called in functional style. Like map(), filter(), reduce(), etc. This step requires a bit functional approach as you have to separate iteration algorithm and cycle body. Body should remain in business domain while cycle iteration itself must be moved to low utility level. Next rule is linked to this.

Rule 5 – Use functional approach to work with your data

First, if you are not yet familiar with functional programming read some books on it – like Thinking Functional (books even could be not related directly to C++). You don’t have to master functional programming, but only get basic foundation principles and understanding of benefits of functional data processing in modern concurrent environments. My position is that you have to combine old OOP paradigm and FP to get the best parts from both of them.

C++11 has std::function, lambdas, closures, etc. Use it! Forget about pointer to functions. Forget about structures you pass to events. Forget about that old stuff which looked more like workarounds. Yes, C++11 will not bring so minimalistic functional code to have fun with, but its enough to implement all the stuff. And even more – you can create your own container class extension methods to work with arrays to make things a lot more compact and shiny. For example, here is the code to get square images from collection:

Or you can add each() method to the list class to call function for each element:

Here i used old indexed cycle to show that this iteration can be encapsulated. When working with such collection you can call only functional-style methods and don’t know nothing about how exactly iteration is made inside. This reduces complexity.

If you are not familiar with lambdas there is one point to know about: you have to control the way you do closures. If you have experience with JS you probably got situations when lambda is inside lambda inside lambda and so on and pass data through this madness. Such unreadable and not maintainable situation should be avoided as much as large number of inner cycles.

Data processing with FP leads to a lot of copy constructing overhead. Good idea is to use shared  pointers to immutable data here.

Last thing here: there are cases when FP is not so suitable. Don’t try to use functional paradigm as hummer everywhere.

Rule 6 – Use auto to compact code and prevent type casting mistakes

But use it with care. First when i hear about auto i thought its a bad thing, because it would produce unreadable code. And it seemed a bit of violation of strong typing. But in practice it appeared quite usable, because there are a lot of cases where you understand what type you have. Often the name of your variable is almost the same as the name of class.

Rare cases when you have a lot of autos grouped together and you feel readability can suffer from it – don’t use it.

Unexpectedly, auto can reveal some type casting issues. This is an additional benefit. You can read Scott Meyers latest book about it to get details.

Rule 7 – Use uniform initalization instead of ‘=’

There are pros and cons inside Scott Meyers book for so called uniform initialisation, but i see it as one of steps forward. It contains strict checks and has more flexible notation. When needed you can define constructors from std::initializer_list to get more code beauty.

Initialisation is looking more logical with immutable structures when in fact you can’t reassign them using ‘=’ as they are untouchable.

Rule 8 – Use nullptr instead of NULL and don’t forget to check pointers

Looks like the simplest rule in list? No. Here should be discussed one important linked problem to care about. When you work with shared pointers and the result of your function can contain nullptr elements you can get crash when try to access null-pointer data. How to avoid this?

First, you can add notNull() method to list class to filter ‘not null’ elements inside your processing chain. But this will not cover all cases. Unfortunately, old way to check things ( if (a != nullptr) { … } ) is still alive and in large range of cases is the most compact way to go. The problem is that functional approach from math is not so compatible with if-branches. Structures like Either in FP are not perfect imho. So this is the place to add some modifications to this list.

Rule 9 – Use templates instead of MACRO when its possible (Strong: use functional factorization instead of template hell)

As C++11 has variadic templates now – so you can cover a lot more cases where previously we had to use macros.

But there is important thing about template programming! Every time you want to use generics think twice if its the only way to make decomposition? One of the most hated things about C++ is its complexity. You might read couple of books like Alexandrescu’s and feel some kind of new flexibility using templates everywhere. But in practice when you use more than one template argument things become less maintainable. I suggest use templates only when you can’t implement your task without them. Best solution would be pure factorization in functional style, but its not possible in large set of cases (even in math). If you have to use generics do it the way so you can fully encapsulate composite structure and keep template arguments to minimum (preferably only one argument). Also class should have obvious purpose for being templated (nice example is object collection of specific type). Remember that your fellow programmers don’t have to make guesses and WTFs looking at code. So avoid overtemplating.

Rule 10 – MINOR RULES

  • Use features from std lib (threads, etc) instead of third party libs
  • Write your classes to avoid destrutors (Rule of zero)
  • Use “using” instead of “typedef” to improve readability (and it can be templated)
  • Use scoped enums to improve readability and to create more type safe code
  • Use =delete; for obsolete and unwanted functions
  • Use override/final

Conclusion

Using new features of C++11 together plus changing your habits and way of thinking to more functional way, gives you whole new C++ perspective. Keeping in mind the advantages of C++ as fast cross-platform solution, and that quite simple convention rules can shift it close to functional languages in terms of stability, makes C++11 good player on market. So now its more your choice to shoot yourself in the foot or not if you have nice C++11 gun.

PS. Im not talking here that C++11 is the best. No no no. But it is definitely not ugly monster as the most people think now. Especially if you wash it a bit.

This list can be extended and modified as my vision might be changed. If you have some suggestions feel free to comment.

  • mg

    “Rule 1 – used shared_ptr” – this is a very bad advise.
    unique_ptr offers such a beautifully clean ownership management. shared_ptr are like global variables – difficult to reason about, impossible to predict the lifetime, access from multiple, unrelated bits of code.

    Plus: they are costly. unique_ptr’s can be entirely optimized-out by the compiler; shared pointer always carries some overhead.

    I would re-phrase it as: Use unique_ptr whenever object is owned, or as a return value/parameter whenever ownership is transferred.
    Use reference or reference_wrapper whenever borrowing an object.
    Use raw pointer ONLY as an optional, non-owning references only if the value can legally be null. If you see something like this in your code:

    assert(ptr); ptr->x();

    you just wrote a bug. Use reference instead.

    I find shared_ptr abuse to be a give-away rookie mistake. They are convenient, powerful, and poorly understood.

    • Victor Laskin

      Thanks for comment. You have nice points in your suggestion, and due to them i divided the rule number one into 2 forms: strong and weak. And weak form tells to use unique_ptr.

      Ill try to argue here a bit about a strong form:
      If you use unique_ptr and don’t pass ownership you, probably, can use RAII instead. If you pass ownership you can still shoot the foot.

      You even wrote how exactly.

      The problem is that controlling ownership could become tricky. Its the same as using raw pointers but on more safe level. You cant forget to delete data, you cant copy them, but you still can move ownership and try to access it after that.

      Shared_ptr solves this problem but not without the price. There is overhead and you should keep it in mind. When you need a bit more optimization you should use other instruments.

      About shared_ptr: i saw a lot of posts demonizing them. Yes – when you start to use them as global data, they become global data. Yes – if you store them in some permanent buffers they will stay in memory. Yes – if you put some mutable object inside and then affect it from different sources, things might become messy. To not avoid such cases – is rookie mistake imo.

      And the most important part – using with immutable data.