You shouldn’t assume accessors are fast

Author: Chloé Lourseyre

“Accessors are fast.” I heard this leitmotiv so many times over the years that I couldn’t not write about it.

Explanation

What’s the use for accessors?

During the researches I made for this article, I was surprised that there actually is a formal definition of accessors1.

You can find this definition in section § 18.3.5 Accessor functions of the fourth edition of The C++ Programming Language (Bjarne Stroustrup).

Long story short, the definition says that, though is unadvised to abuse that feature, you can design a method to read and edit private attributes of a class to write a clean algorithm (I’m paraphrasing).

So, in short, accessors are used as an interface between the private members of a class and the user of this class.

Like any kind of interface, accessors can thus obfuscate how the actual access of the private members is done, to encapsulate more complex code and ease how the class is used.

1. I’m not discussing the fact that there is a pseudo-consensual definition of accessors. However, since this article aims to deconstruct that definition, I won’t rely on it to build my arguments. You can find this pseudo-consensual definition in the following paragraph, where I describe why it is a bad one.

People don’t use accessors properly…

Every day I see people using accessors improperly. Here are a few examples:

  • Writing the simplest get/set for every member of a class without thinking. If your class makes every attributes available with no subtlety and nothing else is private, then it’s not a class, it’s a struct2.
  • Writing accessors that are never used. Don’t write something you don’t need, it’s just polluting the codebase.
  • Realizing that there are not accessors for a given attribute and implementing them without questioning. Sometimes (often), there is a good reason that an attribute doesn’t have accessors.
  • Declaring a getter that is not const (even though it should be). By default, make everything const unless you need it non-const.
  • Declaring a getter that is const even though it shouldn’t be. This one is very rare, but I did see several times writing a dangerous const-cast just to make a getter const. You should avoid writing const-casts at all costs (and the examples I recall there wasn’t a good reason for it).

There are a lot of other bad practices that are too verbose to describe in this article (which purpose is not to teach you to use accessors properly).

2. Here, I abusively use the word class to name a data structure with private members and the struct to name a data structure that is nothing more than a data bucket. This is done on purpose to smooth the read of the article.

…leading to false assumptions

Bad practices lead to a bad way of thinking. And a bad way of thinking leads to false assumptions.

The most widespread false assumption over accessors is the following:

All accessors should be fast to execute.

Assuming accessors are fast is a useless limitation

Without talking about the fact that it can be harmful (if you use a function thinking it is fast, the day it is not you may have a bad surprise), by making this assumption you put a limitation on yourself.

This limitation is not effectively useful (at best, it saves you the time to read the documentation of the accessors — which should not be overlooked anyway), it restrains your ability to innovate and do something useful with your accessor.

Demonstration with a useful pattern

I’m going to show you a pattern that I love to use because it is really useful and saves a lot of lines of code.

Consider this: you have to implement a class that, given several input parameters, provides several outputs (two in our example). You need to use this class to fill two arrays with the outputs, but the parameters don’t always change (so you don’t always have to perform the computation). Since the computation is time-consuming, you don’t want to re-compute when you don’t have to3.

One (naïve) way to do this would be that (full code here https://godbolt.org/z/a4x769jra) :

class FooBarComputer
{
public:
    FooBarComputer();

    // These update the m_has_changed attribute if relevant
    void set_alpha(int alpha);
    void set_beta(int beta);
    void set_gamma(int gamma);

    int get_foo() const;
    int get_bar() const;

    bool has_changed() const;
    void reset_changed();

    void compute();

private:
    int  m_alpha, m_beta, m_gamma;
    int m_foo, m_bar;
    bool m_has_changed;
};


//  ...


//==============================
bool FooBarComputer::has_changed() const
{
    return m_has_changed;
}

void FooBarComputer::reset_changed()
{
    m_has_changed = false;
}


//==============================
// Output getters
int FooBarComputer::get_foo() const
{
    return m_foo;
}

int FooBarComputer::get_bar() const
{
    return m_bar;
}


//  ...


//==============================
// main loop
int main()
{
    std::vector<int> foo_vect, bar_vect;
    FooBarComputer fbc;

    for (int i = 0 ; i < LOOP_SIZE ; ++i)
    {
        fbc.set_alpha( generate_alpha() );
        fbc.set_beta( generate_beta() );
        fbc.set_gamma( generate_gamma() );

        if ( fbc.has_changed() )
        {
            fbc.compute();
            fbc.reset_changed();
        }

        foo_vect.push_back( fbc.get_foo() );
        bar_vect.push_back( fbc.get_bar() );
    }
}

However, it is possible to write a better version of this class by tweaking the getter, like so (full code here https://godbolt.org/z/aqznsr6KP):

class FooBarComputer
{
public:
    FooBarComputer();

    // These update the m_has_changed attribute if relevant
    void set_alpha(int alpha);
    void set_beta(int beta);
    void set_gamma(int gamma);

    int get_foo();
    int get_bar();

private:
    void check_change();
    void compute();

    int  m_alpha, m_beta, m_gamma;
    int m_foo, m_bar;
    bool m_has_changed;
};


//  ...


//==============================
void FooBarComputer::check_change()
{
    if (m_has_changed)
    {
        compute();
        m_has_changed = false;
    }
}


//==============================
// Output getters
int FooBarComputer::get_foo()
{
    check_change();
    return m_foo;
}

int FooBarComputer::get_bar()
{
    check_change();
    return m_bar;
}


//  ...


//==============================
// main loop
int main()
{
    std::vector<int> foo_vect, bar_vect;
    FooBarComputer fbc;

    for (int i = 0 ; i < LOOP_SIZE ; ++i)
    {
        fbc.set_alpha( generate_alpha() );
        fbc.set_beta( generate_beta() );
        fbc.set_gamma( generate_gamma() );

        foo_vect.push_back( fbc.get_foo() );
        bar_vect.push_back( fbc.get_bar() );
    }
}

Here are the benefits:

  • The main code is shorter and clearer.
  • You don’t need to make the compute() public anymore.
  • You don’t need has_changed(), instead, you have a check_change() that is private.
  • Users of your class will be less likely to misuse it. Since the compute() is now lazy, you are sure that no one will call the compute() recklessly.

This is that last point that is the most important. Any user could have made the mistake to omit the if conditional in the first version of the loop, rendering it inefficient.

3. If you want, you can imagine that the parameters change only once a hundred times. Thus, this is important to not call the compute() every time.

Isn’t it possible to do otherwise?

The most recurring argument I hear about this practice is the following: “Well, why don’t you just name your method otherwise? Like computeFoo()?”.

Well, the method does not always perform the computation so it’s wrong to call it that way. Plus, semantically, compute refers to a computation and does not imply that the value is returned. Even if some people do it, I don’t like to use the word compute that way.

“Then call it computeAndGetFoo() and be done with it!”

Except, again, it’s not what it does. It does not always compute, so a more appropriate name would be sometimesComputeAndAlwaysGet(), which is plain ridiculous for such a simple method.

“Then find a proper name!”

I did. It’s getFoo(). It’s what it does, it gets the foo. The fact that the compute() is lazy does not change the fact that it’s a getter. Plus, this is mentioned in the comments that the get may be costly, so read the documentation and everything will be fine.

“Couldn’t you put the check into the compute() instead of the getter?”

I could have, but to what end? Making the compute() public is useless since you need the getters to access the data.

There is a way to do otherwise…

There actually is a way to do otherwise, which is especially useful when you also need a getter than you are sure will never call the compute (this is useful if you need a const getter for instance).

In that case, I advise that you don’t use the same name for both getters, because they don’t do the same thing. Using the same name (with the only difference being the constness) will be confusing.

I personally use the following names:

  • getActiveFoo()
  • getPassiveFoo()

I like this way of writing it because you explicitly specify if the getter is potentially costly or not. Moreover, it also implies that the passive getter may give you an outdated value.

Plus, anyone who tries to call getFoo() will not succeed and have to choose between both versions. Forcing the user to think about what they write is always a good thing.

Most importantly: spread the word

Since it is useful to put performance-sensitive code in getters, you will find developers who do it.

The most dangerous behavior is ignoring that fact and letting other people assume that accessors are fast to execute.

You should spread the word around you that accessors are, at the end of the day, like any other method: they can be long to execute, and everyone must read the associated documentation before using them.

In this article, I talked about execution time performance, but this also applies to any other form of performance: memory consumption, disk access, network access, etc.

Thank you for reading and see you next week!

Author: Chloé Lourseyre

How many languages should a (C++) expert speak?

Author: Chloé Lourseyre

This article is based on a Lightning Talk I made during the CppCon 2020: How many languages a (C++) expert should speak ? – Thomas Lourseyre – CppCon 2020 – YouTube.

The speaker is a bit stressed and fumbles with words, but I’ll try and elaborate on what he wants to says.

Why learning other languages?

Assuming you are a C++ developer, I’ll explain the reasons why you would learn other languages using two quotes.

If all you have is a hammer, everything looks like a nail.

Maslow’s hammer

Say you are given the task to solve a programming problem/issue/dilemma. One of the first questions you will have to ask yourself is “What language should I use to solve this?”. If you only know C++, you can only answer “C++”. Obviously, C++ can not always be the perfect answer.

Even if you are not an expert in other languages, if you know the basics and can say “This language is better suited than C++” then all you’ll have to do is to find the appropriate expert. This is an insight you can’t have if you only know one language.

Those who cannot remember the past are condemned to repeat it.

George Santayana

The programming industry is not a pink paradise where all projects start from scratch and you have all the tools you want in their latest versions. Sometimes (or often, depending on your position in the industry) you will have to work with legacy code. Sometimes it will be written in C++ and you’ll have to port it to another language, and sometimes it will be written in another language and you will need to translate it to C++.

In both cases, this is work you can only do if you know other languages than C++.

Asking the real question

With that said, I can safely state that the real question is not “Should I learn other languages?” but rather the following:

How many other languages should I learn?

The language knowledge table

I designed a table that ranks the languages one knows depending on their knowledge of them:

Level of knowledgeDetailsPersonal examples
Expert levelIt’s OK if you only know one.C++
Practical levelLanguages you tried and loved, and you practice regularly.Python, C, Rust, Bash
Documentation levelLanguages you practiced but didn’t like that much. You know how to fetch documentation though.Java, C#, js, basic, perl
Hello word! levelYou don’t know much about it…Ruby, AS, Go, etc.

You may have noticed that my personal examples have evolved since last year – it’s a good thing.

The levels of knowledge

There are four levels of knowledge depicted in this table:

  • Expert level: These are the languages for which you are expertly reliable. A lot of developers have none, and most of the remaining have one. Some top-tier experts have several, but it requires to be very knowledgeable in both, so it’s quite rare. It is not a mandatory field and it’s OK if you have only one.
  • Practical level: These are the languages you practice regularly but you can not say you’re an expert of. Usually, these are the languages you love and use for your personal projects.
  • Documentation level: There are probably languages you tried to learn, practiced a bit but did not like. Maybe there are some languages you had to learn and practice for a specific project but immediately forgot afterward. These are languages you are not very knowledgeable in, but you know the basics and are able de to document yourself if needed.
  • Hello world! level: These are the languages you basically know nothing about. Languages that you know by name, but wouldn’t be able to give the core specification. Maybe you wrote a “Hello world!” someday, but not much more.

The only useful levels of knowledge are expert, practical, and documentation. As soon as you can at least know where to fetch documentation, you’ll be able to make something out of the language. The Hello world! level is useless in that regard.

How to promote a language?

So your goal will be to try to promote (in the table) as many languages as possible. But the efforts it takes to promote a language depend on the level of knowledge this language stands on:

  • From practical to expert: Becoming an expert in any field is time-consuming. You are not to become an expert unless you want to spend a lot of time on the language. You may want to, but it is a big investment of time you cannot do for the sole sake of being more polyvalent.
  • From documentation to practical: If you try and force yourself to practice a language you don’t love, there is a good chance you will hate the said language even more. You can’t force love, and even you may have a good surprise (and end up loving a language you didn’t like at first sight), over-practicing a language you dislike for the sake of being more polyvalent is just pointless and self-harm.
  • From Hello world! to documentation: What does it take to try and practice a new language to the point you make an opinion about it? In a matter of a few hours, you will be able to read the letter of intent of the language, learn the basics, and grasp how its documentation is organized. You may like it or not, but at least you will be a bit knowledgeable about it.

The most feasible promotion thus is from Hello world! to documentation level. If you are curious and try to learn the whys and hows of as many languages as you can, you’ll have a broader view of the world of programming.

So, how many languages should you speak?

The answer I provide you today, regarding all we said, is the following: as many as your curiosity can bring you to.

One of the main qualities of an expert (be it in C++ or not) is their curiosity. This is what pushes you to learn more, every day, about every subject. As long as you stay curious, you will learn and grow.

Don’t waste your curiosity on a single subject, curiosity is best employed in width that in depth.

On what basis should you learn and/or keep your knowledge of other languages?

The answer to that question is always closely dependent on the free time you can allocate to the activity of coding.

If you have an hour of free time you want to invest in development, pick a language you know by name but never practiced. Read the not of intent and write a simple program. This should be enough for you to know if you want to invest time into this language. If not, keep in mind where you can find the documentation in case you need it later (or bookmark it in your favorite browser, if you don’t want to remember it).

If a language appeals to you, then try to evaluate how much time you can spend training. You can follow tutorials, you can do exercises, you can develop new projects.

There is a good way to keep your knowledge of your favorite languages: Platforms like CodinGame offers training and challenges and include tons of different languages. Try it, it’s very addictive: Coding Games and Programming Challenges to Code Better (codingame.com).

Is it only restricted to C++ experts?

I can hear some of you sighing in front of your screen, “I’m not even a C++ expert, should I even bother learning another language?”.

All the arguments I detailed above are valid for both experts and non-experts. As a non-expert, in addition to that, you are (in my experience) more likely to switch to another language sooner or later. You could wait to get onto a project to learn the language it features, but then you’ll be taking the risk to end up coding in a language you dislike or don’t understand.

Knowing other languages not only makes you more appealing to those who seek new collaborators (most recruiters will prefer someone who trained a bit in the language over someone who never touched it), but you will have a small insight into what the language is all about.

Curiosity is good quality, in software development.

Thanks for your attention and see you next week!

Author: Chloé Lourseyre

Should every variable be const by default?

Author: Chloé Lourseyre

A bit of terminology

In the title, I used the word “const” because const is a keyword every C++ developer knows (I hope). However, the generic concept this article is about is actually called “immutability”, and not “constness”. const is a keyword used in some languages (C++, Javascript, etc.) but the concept of immutability exists in other languages (in some of them, there is even no keyword attached to the concept, like in Rust).

To be a little more inclusive, I will use the words immutable and immutability instead of const and constness.

Nota bene: in C++ the keyword const does not completely refer to immutability. For instance, you can use the keyword const in a function prototype to indicate you won’t modify it, but you can pass a mutable object as this parameter. However, this article only refers to const as a way to render data immutable, so I would be grateful if you ignore situations where const does not refer to inherently immutable data. That is also another reason I wish to use the word immutable and not const.

Some very good reasons to make your variables immutable

Safety

What is called const-correctness in C++ is the fact that if you state that a given variable is immutable, then it won’t be modified.

Const-correctness is reached when you use the const keyword to tell when a variable won’t be modified. Then, you won’t be able to inadvertently modify something you shouldn’t and you give insight to the compiler and the developers of the data you manipulate.

Documentation

These five characters tell a lot. If you find yourself in a reliable code, where you are sure that const is used properly, then this keyword -or its absence- serves as an indication of the intended mutability of the variable.

Optimization

The compiler can (and will) optimize the code according to the const directives.

Knowing that some values are immutable, the compiler will be able to make assumptions and use syntactic shortcuts.

You can feed on J. Turner’s talk on the subject of practical performance: Jason Turner: Practical Performance Practices – YouTube.

Source

If you still doubt that immutability is useful in C++ (or are just curious, which you should be), please read these:

Are there downsides to using immutability whenever possible?

Since immutability is a technical restriction, there is no technical downside to using it.

However, there may be some non-technical downside of using const. First, it increases the verbosity. Even if it’s a 5-letters word that often has the same color as the type (in most IDEs), some people might find this impairing. There is also inertia: if you need to modify the variable, you’ll have to go and remove the keyword. Some may consider it a safeguard, others an impairment.

Are these arguments enough to not use immutability as soon as you can? It’s up to you, but in my humble opinion, I think not.

Other languages?

Other languages make variables immutable by default.

One famous example is the Rust language. In Rust, you don’t have the const keyword. Instead, every variable is by default immutable, and you can add the mut keyword to make them mutable.

For instance, in Rust, the following code doesn’t compile:

fn main() {
    let foo = 2;
    println!("Foo: {}", foo);
    foo += 40; // error[E0384]: cannot assign twice to immutable variable `foo`
    println!("Foo: {}", foo);
}

But this is correct:

fn main() {
    let mut foo = 2;
    println!("Foo: {}", foo);
    foo += 40;
    println!("Foo: {}", foo);
}

The reason mentioned in the Rust Handbook is the following: “This is one of many nudges Rust gives you to write your code in a way that takes advantage of the safety and easy concurrency that Rust offers.”

Rust is a pretty modern language that aims to provide safety to the developer along with top-tier performances. I agree with its intention to make variables immutable by default because of the safety it brings.

Is it reasonable to change the standard to make variables immutable by default?

If one fateful day the C++ committee decides to make every variable const by default… no one will upgrade to the vNext of C++.

Retro compatibility is important to smooth the transition between vPrevious and vNext. There are few historical examples where a feature has been removed from the standard, and for each, there is a really good reason why (often being years of deprecation).

But we can’t seriously consider removing the keyword const and add the keyword mut just for the sake of what is, in the end, syntactic sugar.

What to do then?

I for one chose to use the const keyword every single time I declare a variable. I only remove it when I actually write an instruction that requires it to be mutable (or when the compiler realizes it needs to modify a constant variable and outputs an error). This is a little blunt, but in my opinion, it’s the best way to ensure to catch the habit of putting const by default.

Of course, you may try to be smart at the moment you declare the variable and try to foresee the use you’ll make of it, but I guarantee you will not be 100% reliable and miss opportunities to make variables immutable.

So here’s my advice: until you catch the habit of putting const everywhere, use const by default without double thinking. You may have regular easy-to-solve compilation errors, but you’ll catch a good habit.

And what about constexpr?

constexpr is a modifier that indicates that the value of a variable can be evaluated at compile time. Its purpose is to transfer some evaluation to the compilation phase.

In a sense, it is stronger immutability than the keyword const. All I said in this article about immutability applies to constexpr: use it as often as you can.

However, unlike const, I don’t advise trying to use it everywhere and see afterward if it needs to be removed, since it’s way rarer and a bit more obvious to use. But keep in mind to check if you can use constexpr every time to declare a variable.

Thank you for reading and see you next week!

Author: Chloé Lourseyre

Don’t use raw loops

Author: Chloé Lourseyre

Intentions

Sean Parent once said “No raw loops“. This was eight years ago.

Today, among the people who don’t have a strong C++ background, almost nobody knows this statement (let alone to know who Sean Parent is). As a result, in 2021, many C++ projects use tons of raw loops and almost no algorithms.

This article intends to teach you, the people who still use raw loops in 2021, why and how to use algorithms instead.

Resources

This subject has been covered by many C++ experts already, so if you feel technical and have some free time, check the following resources:

Sean Parent’s talk that contains the no raw loops statement:
GoingNative 2013 C++ Seasoning – YouTube

A Jason Turner’s talk about code smells (including the use of raw loops):
C++ Code Smells – Jason Turner – CppCon 2019 – YouTube

Jonathan Boccara’s talk about STL algorithms:
CppCon 2018: Jonathan Boccara “105 STL Algorithms in Less Than an Hour” – YouTube.

As a bonus, here is a map representation of the STL algorithms:
The World Map of C++ STL Algorithms – Fluent C++ (fluentcpp.com).

What are raw loops?

What we call raw loops are you most basic design of loops. It is usually the use of the keywords for, while, or do while accompanied by a block of code.

Raw loops are opposed to algorithms, that are encapsulated loops (or non-loops, how could you know since it’s encapsulated?) serving a specific or generic purpose and that can be called using dedicated functions.

Why you should not use raw loops

It’s a matter of semantics. A raw loop can only express the technical fact that it’s a loop, you write how your algorithm proceeds.

When you call an algorithm, you write what you intend, you write what you want to obtain.

For example, let’s look at this sample of code:

//...

for (size_t i = 0; i < my_vect.size() && predicate(my_vect, i) ; ++i)
{
    //...
}

//...

This tells you that you perform a loop, indexed over a vector and controlled by a custom predicate, but no more. It doesn’t tell you what the loop does, and what result is expected from it. You’ll have to study the body of the loop to know that.

Now let’s look at this algorithm call:

//...

auto it_found = std::find_if(cbegin(my_vect), cend(my_vect), predicate);

//...

Even if you don’t know how find_if() proceeds, you understand it will return an element of my_vect that matches the condition predicate(). You understand what it does, not how it is done.

From there, there are several points to consider:

  • Algorithms raise the level of abstraction and help you understand the intention behind the code.
  • Good semantics leads to good readability, good readability leads to better maintainability, better maintainability leads to fewer regressions.
  • Calling an algorithm is less verbose than re-writing it.
  • Raw loops are prone to several common mistakes, like off-by-one, empty loops, naive complexity, etc.

What to do when there is no existing algorithm?

There are times when you need an algorithm to perform a specific task, but this is so specific that none of the existing algorithms is suitable. What to do in this case?

Combine algorithms into a new one

Often, your specific algorithm can be resolved by doing a combination of existing algorithms, or by implementing a specific version of an existing one. To do so, just implement a function that does the combination for you and give it an explicit name. Then anyone can call it like any algorithm.

For example, you need to verify if, in a vector, all elements that match condition A also match condition B. To do this, you can use the algorithm std::all_of with a custom predicate:

template< typename Iterator, typename PredA, typename PredB >
bool both_or_none( Iterator first, Iterator last, PredA & predA, PredB & predB )
{
    auto pred = [&predA,&predB](const auto& elt)
    {
        return predA(elt) == predB(elt); 
    };
    return all_of(first, last, pred);
}

The body of the algorithm is pretty short: it creates a function that combines both predicates to implement our specific condition, then applies std::all_of(), the algorithm that verifies that the condition is true on every element of the collection.

Write a raw loop inside a function

There are some times when trying to combine existing algorithms is futile and may feel forced and artificial.

What you need to do when this occurs is to write your own raw loop in a dedicated function, that will act as your algorithm. Be sure to give it an explicit name.

For example, you have a collection and you need to get the maximum element that matches a condition. This is what the algorithm max_if() would be if it existed.

However, you can’t pull it out easily by combining existing algorithms. You would first need to get the subset of your collection that matches the condition, then calling std::max() on it. But the only way1 to get that subset is to use a std::copy_if, which copies elements. Copies may be expensive so you don’t want that.

What to do then? Write the raw loop that implements the max_if() yourself, and encapsulate it in a function:

template< typename Iterator, typename Pred >
constexpr Iterator max_if( Iterator first, Iterator last, Pred & pred )
{
    Iterator max_element = last;
    for (auto it = first ; it != last ; ++it)
    {
        if (pred(*it) && (max_element == last || *it > *max_element))
            max_element = it;
    }
    return max_element;
}

Then, the use of max_if will be semantically explicit, matching all the upsides of a true algorithm.

1There actually are other ways, like using find_if then remove in successions, but they are pretty much worse.

STL algorithms examples

There are plenty of algorithms in the STL. I suggest you be curious and explore by yourself: Algorithms library – cppreference.com.

As an appetizer, here are a few very basic and common algorithms that, if you don’t already know them, should learn about.

  • std::find(): Searches for an element equal to a given value.
  • std::find_if(): Searches for an element for which a given predicate returns true.
  • std::for_each(): Applies the given function object to the result of dereferencing every iterator in the range, order.
  • std::transform(): Applies the given function to a range and stores the result in another range, keeping the original elements order.
  • std::all_of(): Checks if the given unary predicate returns true for all elements in the range.
  • std::any_of(): Checks if the given unary predicate returns true for at least one element in the range.
  • std::copy_if(): Copies the elements for which the given predicate returns true.
  • std::remove_if(): Removes the elements for which the given predicate returns true.
  • std::reverse(): Reverses the order of the elements in the range.
  • And many more…

If you want to go further, here is a one-hour talk presenting more than a hundred algorithms: CppCon 2018: Jonathan Boccara “105 STL Algorithms in Less Than an Hour” – YouTube

Wrapping up

Many C++ experts agree that loops are to disappear in the higher abstraction levels, only used to write lower-level algorithms. This statement is not an absolute, but an ideal to keep in mind when you code.

If like many C++ developers, you tend to use raw loops instead of algorithms, you certainly should check the resources provided in this article. As you get familiar with the most basic algorithm and begin to use them in practice, you’ll find them more and more convenient.

Thanks for reading and see you next week!

Author: Chloé Lourseyre

Exceptions are just fancy gotos

Author: Chloé Lourseyre

About goto

Back to basics: why is goto evil?

I could just throw the link to the article E. W. Dijkstra wrote (Go To Statement Considered Harmful (arizona.edu)), which explains pretty well why goto is evil, but I’d like to go a little further than that.

I will provide here a list of reasons to not use goto, summarizing the article of Dijkstra and adapting its reasoning to modern programming:

  1. The goto control flow is unstructured, unlike the other control flows in C++. if and loop statements are geographically linked to the code they control. It is either linked to a block (with its own lifecycle) or a single instruction. while reading it, any developer can see and understand what code is nested within the if or the loop and where it ends. Functions, another control flow in C++, are access points. Their signature is a contract that is secured by the compiler. You give input, you get a return value. gotos are unstructured as they are a one-way trip across code, with no geographical attachment like ifs and loops and no entry-exit guarantee like functions. This is very prone to unmaintainable, spaghetti code. Long story short: it breaks the program continuity.
  2. Regarding what it can do, goto is probably the most unsafe keyword in the language. I won’t give lengthy examples here, but you can try it out yourself: the number of stupid things you are allowed to do with goto is surprisingly high. And if you do stupid things, you may have crashes, sometimes random, memory corruption, undefined behavior…
  3. Here and today, in the era of modern C++, nobody uses goto anymore. That means most of us are unfamiliar with it. Most developers know they shouldn’t use it, but a whole lot of them don’t know why. Thus, if they ever encounter a goto in their codebase, they are most likely to either refactor the code (and possibly cause a regression, since they are unfamiliar with it) or leave it that way without trying to understand what it does. Since it’s complicated to use, the fact that nobody uses it regularly is yet another argument against it.

There are specific contexts and language where goto can be considered a good practice, but in modern C++, goto is a no-go.

About control flow and spaghetti code

The first point of the previous section (which is the main argument against goto) is talking about control flow and spaghetti code. But why is this important?

You can see an execution of your program as a rope that is unwound alongside your code as it is executed. As long as there are only simple instructions, the rope is unwound normally. When there is control flow, the rope will behave differently.

When the execution encounters a for or while, the rope will trace loops around the attached block, one rope loop for each execution loop that is performed. When the execution encounters an if (and possibly an else), the rope may or may not jump above the block of statements attached to it, depending on the condition, then continuing its course.

When the execution encounters a function, a strand of the rope will do a curl where the function is, then come back at the rope when it’s over, making the rope whole again.

However, when a goto statement is encountered, sometimes you will have no choice but to stretch the rope across your entire code to reach the target label. If there are multiple gotos at multiple locations, then the rope will cross itself over and over again.

If you have good control flow, then you will be able to follow the rope from the beginning to the end. But if your control flow is messy, then the rope will overlap itself all over and you will have trouble understanding any of it. This is what we call spaghetti code, a control flow that looks like a plate full of spaghetti.

Since gotos make the rope cross over the program, it is very prone to spaghetti code.

Is goto really evil?

But, despite all that, can’t we imagine a simple, safe way to use goto? After all, goto is only evil if it’s used to make spaghetti code, but if we write a code that only uses goto locally, in a closed and controlled space, then the code would not be very spaghetti-ish, would it?

There are designs where the use of goto makes the code clearer than with the use of other control flows.

The classic example is with nested loops:

//...

bool should_break = false;
for (int i = 0 ; i < size_i ; ++i)
{
    for (int j = 0 ; j < size_j ; ++j)
    {
        if (condition_of_exit(i,j))
        {
            should_break = true;
            break;
        }
    }
    if (should_break)
        break;
}

//...

If we write it using goto, it will be shorter and a bit clearer:

// ...

for (int i = 0 ; i < size_i ; ++i)
{
    for (int j = 0 ; j < size_j ; ++j)
    {
        if (condition_of_exit(i,j))
            goto end_of_nested_loop;
    }
}
end_of_nested_loop:

// ...

See? goto is not so useless after all!

So should we use this keyword in those specific cases where it makes the code clearer? I think not.

It’s hard to find a variety of examples where goto is better than any other control flow, and nested loops like this one are very scarce. And even if the code is shorter, I don’t find it clearer. There are 2 block levels of difference between the goto and its label, making the jump from the former to the latter counter-intuitive. The human factor remains a huge problem.

So is goto really evil? No, but it’s still an absolute bad practice.

About exceptions

Exceptions: the modern way to break control flow

Exceptions are a way to manage errors in your code. You can also use it as a standard control flow, since you can customize your own exceptions, throw them and catch them however you want.

I like to see exceptions as “dangling ifs”: if your code performs adequately, everything will be good, but if something is off, you just throw everything upwards, hoping that something in the higher-level program will catch it.

Exceptions do break the standard control flow. To take the image of the rope again, when you call a function that can throw an exception, then a strand of the rope will go into the function (just like before), but you’ll have no guarantee that the strand will be returned to the rope and the place where you called the function. It can be re-attached anywhere above in the call stack. The only way to prevent that is to try-catch all exceptions when you call the function, but this is only possible if you know what to do in every case of error.

Moreover, when you write a function in which you may throw an exception, you have no way to know if and where it will be caught.

Though this is not as bad as a goto, because it is more controlled, you can still easily write spaghetti code. With this hindsight, we can consider that exceptions are a fancy, “modern” equivalent of goto.

We can even write the nested-loops-escape with exceptions:

//...

try 
{
    for (int i = 0 ; i < size_i ; ++i)
    {
        for (int j = 0 ; j < size_j ; ++j)
        {
            if (condition_of_exit(i,j))
                throw;
        }
    }
}
catch (const std::exception& e)
{ /* nothing */ }

//...

I wouldn’t recommend it though. Exceptions are evil.

Are exceptions really evil?

…are they? Not really.

In the previous section, I stated that goto isn’t inherently evil, but is bad practice because it is really error-prone for what there is to gain from it. The same goes for exception: it’s not pure evilness, it’s a just feature.

In my opinion, unlike goto, there are ways to use exceptions safely.

One major difference between exceptions and gotos

To understand how to correctly use exceptions, we must understand the differences between them and gotos.

Exceptions do not send the execution somewhere undefined, it sends the execution upward in the stack. It can be a few blocks upward (like in the nested-loops example) or several function calls upward.

When to use exceptions?

Sending the program execution upward is still pretty undefined, and in most cases, we don’t want to break the program continuity.

In most cases.

There is one specific situation when we want the execution to stop abruptly: when someone misuses a feature in a way that could cause an unwanted behavior (or worse).

When you write a feature, you want to be able to stop any degraded state from happening, and instead send the execution upward and say “something unexpected happened, I can’t proceed”. Not only this will prevent any unwanted behavior, but it will also warn the user that they misused your feature, forcing them to either correct their use of it or handle the error they caused.

When you write a feature, there is a virtual wall between you and the user, with a tiny hole that is represented by the feature’s interface. It’s up to each of you to handle how the fictional rope behaves on each side of the wall.

A feature can be a whole library or a single class, but as long as it’s encapsulated, it’s okay to use exception as part of their interface.

A good example of that is the at() method of std::vector<>. The method’s goal is to return the nth element of the vector, but there is a chance that the user ask for an out-of-bound element. In that case, throwing an exception is a good way to stop the vector from causing an undefined behavior. If the user catches the exception, they can write a code to execute in case of OOB index. If not, then the program stops and indicates that an out-of-bound has been raised, forcing the user to secure their code or handle the degraded state.

In any case, you must document every exception your code can throw.

Wrapping up

I feel like many of the articles I write could just be concluded by “just don’t be stupid”, but I always try to give a good summary anyway, because not being stupid is actually harder than it looks (I am myself sometimes quite stupid, and it happens more times than I would admit).

It is especially hard to not be stupid with exceptions, considering how easy it is to blow everything up. I will try to summarize the good practice to apply regarding exception in three points:

  • Do not use exceptions for flow control.
  • You can use exceptions as part of your feature interface.
  • Document every exception your feature can throw.

Thanks for reading and see you next week!

Author: Chloé Lourseyre

Dealing with integer overflows

Author: Chloé Lourseyre

Integer overflows are a pain. There is no consistent and easy way to detect them, let alone to statically detect them, and the will make your program inconsistent.

This article will talk about both signed and unsigned integer overflow, without distinction. Even if signed overflow is undefined behavior while unsigned is not, 99.9% of the time you want neither of them.

How to detect integer overflows?

While they are few, there are ways to detected integer overflows. They just either lack consistency or easiness.

I will present a few way to detect overflow in this section. If you happen to know other ways to do so, feel free to share it in comments, so it can benefit to everyone.

UBSan

Undefined Behavior Sanitizer, UBSan for short,  is a runtime undefined behaviour checker.

It has the ability to detect integer overflows in the form of compilation options (though it is supposed to check UBs, it also do us the favor to check unsigned overflows):

clang++ -fsanitize=signed-integer-overflow -fsanitize=unsigned-integer-overflow

It is implemented for both clang and GCC:

I may be wrong, but I did not see any integration in other compilers.

The main downside of a dynamic checker like this one is that you’ll have to do a full compilation and an exhaustive test run of your program to detect overflows. If your test run is not exhaustive enough, you run the risk to let an overflow slip.

Write adequate unit tests

If your project implement unit tests (I could argue that every project should, but it’s not always up to the developers), then you have a pretty direct way to check for overflows.

For the data and functions that can accept very high numbers, just throw big ints into them. You can then check if the features perform correct evaluations, throws an exception, returns an error code, or do whatever it is supposed to do in these cases.

If you don’t know what result to expect when you put a big int into them, because the result would be too big, and there is no error-handling, then your feature is unsafe. You must put so error-handling in them to ensure no overflow will happen.

Sometime, detecting a potential overflow this way will requires weighty refactoring. Don’t be afraid to do it, you are better safe than sorry.

Don’t put your code in a situation prone to overflows

The best way be sure there is no overflow is to prevent overflows. If you ensure that no overflow can arise from the code you write, you won’t have to detect them.

The next section will give a few practices to help you in that regard.

How to prevent integer overflows?

Use 64-bits integers

One very good way to prevent integer overflows is to use int64_t to implement integers. In most case, 64-bits ints will not commit overflow, unlike their 32-bits counterparts.

There is actually very few downsides in using int64_t instead of int32_t. Most of the time, you won’t care about the performance gap or the size gap between them. Only if you work on embedded software or on processing algorithms you may care about performance or data size constraints (by “processing algorithms” I mean algorithms supposed to have top-notch performance, like in signal processing).

Just note that longer integers does not always mean slower calculation, and with the full set of pointer / reference / forwarding, you don’t copy whole data structures very often.

And in any case, even if you are performance and/or size sensitive, always remember:

First, be safe. Then, be fast.

(citation by me)

Trying to over-optimize and risk integer overflow is always worse than to write safe code then use tools to pinpoint where the program must be optimized.

So I recommend that, unless your integer has a hard limit and is very small, use a int64_t.

About int64_t performances

I ran a few basic operations (addition, multiplication and division) on both int32_t and int64_t to see if there were any significant differences.

Using clang, using two different optimization levels, here are the results:

This may not surprise you, but in every case it’s the division which is the slowest. If you don’t have to use divisions, you will notice no performance gap between 32 bits and 64 bits. However, if you opt for divisions, 32-bits integers are more suitable (but just because you’re using divisions you won’t have top-notch performance).

Gentle reminder about basic types

If you ever wonder why I used the types int32_t and int64_t instead of the plain old int and long, it’s because these two type don’t have a pre-defined size.

The only size constraints that the C++ standard applies on int and long are the following:

  • int is at least 16 bits
  • long is at least 32 bits.
  • The size of long is greater than or equal to the size of int, bool and wchar_t, and is lesser than or equal to the size of long long
  • The size of int is greater than or equal to the size of short and is lesser than or equal to the size of long

Because of that, please refrain from using plain old ints and longs when you want to avoid overflows.

Don’t assume that because a value is in range, it’s overflow-safe

Let’s say, you have a int32_t my_val that represents a data which max value is one billion (1 000 000 000). Since the max value of a int32_t is 231-1 (2 147 483 647), you may think it won’t cause overflow.

But one fateful day, an random dev unknowingly writes this:

#include <cstdlib>

const int32_t C_FOO_FACTOR = 3;

int32_t evaluate_foo(int32_t my_val)
{
    // foo is always C_FOO_FACTOR times my_val
    return my_val * C_FOO_FACTOR;
}

You called it? Integer overflow. Indeed, there are values of my_val that can cause an overflow when multiplied by 3.

So whose fault it is? Should we check for an overflow when we add or multiply? How could we do this?

Well, there is one simple practice that can help you avert most of the cases similar to this example. When you have to store an integer that is relatively big, even if it can’t overflow by himself, just put it in a bigger data type.

For instance, I never put a value that can be bigger than 215 in a data type that can hold the max value of 231. This way, even if we multiply the value with itself, it doesn’t overflow.

With this method we can keep lower data types in smaller structure with no side-effect. C_FOO_FACTOR can stay as a int32_t, the result will be adequately promoted if it’s used in an operation including a bigger type.

E.g.:

#include <cstdlib>

const int32_t C_FOO_FACTOR = 3;

int64_t evaluate_foo(int64_t my_val)
{
    // foo is always C_FOO_FACTOR times my_val
    return my_val * C_FOO_FACTOR; // The result of the multiplication is a int64_t
}

Use auto

Yes, auto can sometime be a lifesaver when you’re not 100% sure of the types you’re dealing with.

For instance:

#include <cstdlib>

int32_t  C_FOO = 42;

int64_t compute_bar();

int main()
{
    // Big risk of overflow overflow here
    int32_t foo_bar = compute_bar() + C_FOO;

    // Probably no overflow here
    auto better_foo_bar = compute_bar() + C_FOO;
}

Here, auto is useful because it prevents the error committed line 10, where the result of the operation compute_bar() + C_FOO, which is a int64_t, is converted back to a int32_t. Line 13, auto will become int64_t so no overflow will occur because of that.

(Note: integer demotion — meaning converting back to a smaller type — is actually an overflow).

There is also another specific case, one that doesn’t occur often, where auto can be useful. Consider the following code:

#include <cstdlib>

int32_t  C_FOO = 42;

int32_t compute_bar();

int main()
{
    // Big risk of overflow overflow here
    auto foo_bar = compute_bar() + C_FOO;
}

There, the return value of compute_bar() is int32_t. But later, the author of this function, seeing that the type is too small, changes it to a int64_t, like this:

#include <cstdlib>

int32_t  C_FOO = 42;

int64_t compute_bar();

int main()
{
    // Big risk of overflow overflow here
    auto foo_bar = compute_bar() + C_FOO;
}

Here, the auto automatically “promoted” to int64_t, avoiding an implicit conversion. If it was int32_t instead of auto at the beginning, then there would have a risk that the developer who edited the signature of compute_bar() did not correct the type of the variable foo_bar, without rising any compilation error or warning. So the usage of auto in this case made us dodge the bullet.

Wrapping up…

Always beware, when you’re dealing with big integers, to use big data type. Use auto when you’re unsure of what you’re dealing with, and use analyzers if you think the code may hold an overflow. And, as always, write good unit tests.

If you personally know of other ways to detect and/or prevent integer overflows, feel free to share in comments.

This is overhaul the best you can do to avoid and detect integer overflows.

Thanks for reading and see you next week!

Author: Chloé Lourseyre

A list of bad practices commonly seen in industrial projects

Author: Chloé Lourseyre

If you ever worked in a company-size software project (with numerous developers), there is a good chance that the codebase was, at least, pretty messy.

In these days, most industrial C++ developers are not experts. You will often work with developers with a Java or Python background, people who just learnt C++ at school and don’t really care that much about the language and old developers who code in “C with classes” instead of C++.

Having worked on a few industrial projects myself, I realized there are some recurring patterns and bad practices. If you happen to teach your coworkers to avoid these bad practices, you will all take a huge step toward a beautiful codebase and it will be beneficial to you, your coworkers and your project.

Here is a non-exhaustive list of these common bad practices.

Bad practice : Overly long functions

I am not one to set hard restrictions over the number of lines in a function. However, when a function reach more that a thousand lines (or even tens of thousands of lines), it is time to put a stop at it.

A function is a architectural block. If it is too big, it will be harder to understand. If is it split into different blocks, with explicit names and comprehensible comments, your mind will be able to turn its attention to each blocks in turns, which are individually easier to understand, and will put them back together to understand the globality of the function.

Sometimes, your function is just calling auxiliary functions in succession, and that’s ok. It’s short, easy to understand, and each auxiliary function, which are small, are also easy to understand.

To solve a big problem, split it in several smaller problems.

The limit I generally use is 200 lines per functions. Sometimes more, sometimes less.

Bad practice : Create classes when you don’t need to

This is something that is surprisingly fairly common, and probably due to other object-oriented languages that force you to use classes for everything.

There are two ways this bad practice can occur :

Full-static classes (sometimes with constructors)

It is easier to illustrate with an example, so here we go :

class MyMath
{
public:
    MyMath();
    ~MyMath();
    static int square(int i);
};

MyMath::MyMath()
{
}

MyMath::~MyMath()
{
}

int MyMath::square(int i)
{
    return i*i;
}

int main()
{
    MyMath mm;
    int j = mm.square(3);
    return j;
}

Here are the problematic points :

  • Why would you implement useless constructor and destructor, where you just could have used the default ones ?
  • Why would you implement a constructor and a destructor for a full-static class ?
  • Why would you instantiate an object just to call the static method of the class ?
  • Why would you use class at all, where a namespace would suffice ?

Here is what should have been written :

namespace MyMath
{
    int square(int i);
};

int MyMath::square(int i)
{
    return i*i;
}

int main()
{
    int j = MyMath::square(3);
    return j;
}

Shorter, better, smarter.

True, sometimes a full-static class can be useful, but in situations like that example, they are not.

There is no benefit in using a class where you could not. If you are worried that the namespace could be used as a class in the future (with attributes and methods), just remember this little rule that every one should know :

Do not code thinking of an hypothetical future that may or may not occur. The time you spend coding in anticipation is most certainly wasted, as you can always refactor later.

Fully transparent classes

I put this one in second because it is the most controversial.

Just to clear : the only difference between class and struct is that, by default, the members of a class are private and the members of a struct are public. This is truly the only difference.

So, if your class :

  • … only has public methods
  • … has both accessors (getter and setter) to all its attributes.
  • … has only very simple accessors.

… then it’s not a class, it’s a struct.

Here, to illustrate :

class MyClass
{
    int m_foo;
    int m_bar;

public:
    int addAll();
    int getFoo() const;
    void setFoo(int foo);
    int getBar() const;
    void setBar(int bar);
};

int MyClass::addAll()
{ 
    return m_foo + m_bar;
}
int MyClass::getFoo() const
{
    return m_foo;
}
void MyClass::setFoo(int foo)
{
    m_foo = foo;
}
int MyClass::getBar() const
{
    return m_bar;
}
void MyClass::setBar(int bar)
{
    m_bar = bar;
}

Is better written that way :

struct MyClass
{
    int foo;
    int bar;

    int addAll();
};

int MyClass::addAll()
{ 
    return foo + bar;
}

This is pretty much the same. You just withdraw a (useless) level of encapsulation for a more concise and more readable code.

The controversial part occur one the “useless” statement just above, because in a full-object mindset, no encapsulation is useless. In my opinion, this kind of structure don’t need encapsulation because it is just a data structure, and I don’t like when people overdo the concept of encapsulation.

Watch out, though, because this practice is only valid if all your attribute are in direct-access in both writing and reading. If one of our attribute need a specific accessor or you have read-only or write-only attributes, don’t use a struct (well, you can, but you need to seriously think about it).

Bad practice : Implementing undefined behavior

To say it short, an undefined behavior is a promise you make to the compiler that some behavior will never be implemented by your hand. Using that, the compiler will be able to make assumptions and optimize your code with those assumptions.

Go watch the talk of Piotr Padlewski : CppCon 2017: Piotr Padlewski “Undefined Behaviour is awesome!” – YouTube. It will teach you everything you need to know about UB.

Here is a non-exhaustive list of undefined behaviors. You need to know that list by heart in order to avoid unexpected undefined behavior in you codebase :

  • Calling main
  • Integer overflow
  • Buffer overflow
  • Using uninitialized values
  • Dereferencing nullptr
  • Forgetting the return statement
  • Naming variable starting with double underscore
  • Defining function in namespace std
  • Specializing non-user defined type in namespace std
  • Taking the address of a std function

So, it has to be said once and for all : do not ever rely on integer overflow to end a loop or for anything else. Because it does not mean what you think it means and one fateful day it will backfire hard.

Bad practice : Comparing signed and unsigned integer

When you compare signed and unsigned, an arithmetical conversion will occur that has a very good chance to distort the values, thus nullifying your comparison.

Use size_t when it’s relevant, and static_cast your variable if needed.

Bad practice : Trying to optimize the code as you write it

Yeah, that pill may be pretty hard to swallow. But here are two facts :

  • 80% of the time, the code you write doesn’t need to be optimized. Since most of your execution only occurs in 20% of your program (Pareto principle at work), the remaining 80% does not need to be optimized.
  • Optimization should not be a prior concern. You are to write your code, see the big picture, and optimize in consequence.

What is the most important is not how optimized is your program. The thing you should concern yourself about is whether your code is tidy, concise and maintainable. If it is, you can only come back later to optimize it.

Bad practice : Being too dumb

On the opposite, you shouldn’t under-optimize either.

You must know the specificities of the algorithms and the data structures in order to use the correct ones in your code. You need to understand some design patterns and be able to implement them so you don’t reinvent the wheel each time, you mustn’t be afraid to check the documentation before using a feature.

There is a good balance between coding without thinking and over-optimizing the code while you are writing it.

Bad practice : “If we need to do that later…”

I said it a few paragraphs above, but don’t plan on a hypothetical future. The only future you can be sure of is your most basic design. Your needs may change, your specs may change, what the client wants may change, anything beside the core design of your project may change. Sometimes it won’t, but often it will. Be sure to remember that.

When in doubt, be sure to ask yourself :

If I implement that later, will it cost more ?

Often, the answer is “no” or “not that much“. When so, leave the future to the future.

In the end…

Here you have a good start to write beautiful and maintainable code. If every dev on your project apply them, you will all benefit from it.

However, there are many many other basic bad practices I didn’t cover in this article, so the subject is not closed. Maybe I will publish a part 2 someday, maybe not.

Thanks for reading and see you next week!

Author: Chloé Lourseyre

Lambdas as const ref

Author: Chloé Lourseyre

Context

This week, I will present you a piece of code I bumped into not so long ago.

It looked like that:

#include <vector>
#include <algorithm>

int count_even(const std::vector<int>& v)
{
    const auto& my_func = [] (int i)->bool
    {
        return i%2==0; 
    };

    return std::count_if(std::cbegin(v), std::cend(v), my_func);
}

Seeing this, VisualStudio was unsure if it should compile or not.

And by unsure, I mean that a compilation error stating something like “my_func is used but it is already destroyed” was periodicly prompted then discarded during the compilation of the library. But in the end, it compiled.

When I saw this, I thought two things:

“Wait, we can bind a temporary to a const ref?”

and

“What’s the use of binding a lambda to a const ref?”

These are the questions I will answer today.

Binding a rvalue to a const reference

To be short: yes, you can bind a rvalue to a const ref.

Intuitivly, I would have said that trying to do so will only result in a dangling reference, but it will not.

This language is smart, I tend to forget it, but it is only logical that if you try to bind a cons ref to a temporary object, the object will be put on the memory stack and accessed via the const ref.

To illustrate this, here are two functions:

void foo()
{
    const int& i = 1;
    const int& j = i+1;
}

void bar()
{
    int iv = 1;
    const int& i = iv;
    int jv = i+1;
    const int& j = jv;
}

The function foo direclty binds the rvalues to const refs, as the function bar instanciate const values before binding them to cons refs (thus binding a lvalue to a const ref).

The assembly code that clang generate for this piece of code is the following:

foo():                                # @foo()
        push    rbp
        mov     rbp, rsp
        mov     dword ptr [rbp - 12], 1
        lea     rax, [rbp - 12]
        mov     qword ptr [rbp - 8], rax
        mov     rax, qword ptr [rbp - 8]
        mov     eax, dword ptr [rax]
        add     eax, 1
        mov     dword ptr [rbp - 28], eax
        lea     rax, [rbp - 28]
        mov     qword ptr [rbp - 24], rax
        pop     rbp
        ret
bar():                                # @bar()
        push    rbp
        mov     rbp, rsp
        mov     dword ptr [rbp - 4], 1
        lea     rax, [rbp - 4]
        mov     qword ptr [rbp - 16], rax
        mov     rax, qword ptr [rbp - 16]
        mov     eax, dword ptr [rax]
        add     eax, 1
        mov     dword ptr [rbp - 20], eax
        lea     rax, [rbp - 20]
        mov     qword ptr [rbp - 32], rax
        pop     rbp
        ret

These two functions are (except from the stack alignment details, which are not really important) the same.

This fact is confirmed by the IBM documentation: Initialization of references (C++ only) – IBM Documentation, and, of course, by the standard (The C++11 Programming Language, §7.7.1).

This is a very simple fact that is really clear in the standard, but it is rarely used in code and referenced on the web.

The reason for that is that binding to a const ref instead of a const value – or even a plain value – seems useless.

But is it?

Binding a lambda to a const ref

Coming back to the initial context, the question was why it is legal to bind a lambda to a const ref and if it is useful.

As a reminder, here is the example I showed you earlier:

#include <vector>
#include <algorithm>

int count_even(const std::vector<int>& v)
{
    const auto& my_func = [] (int i)->bool
    {
        return i%2==0; 
    };

  	return std::count_if(std::cbegin(v), std::cend(v), my_func);
}

When we put it in C++ Insights (cppinsights.io), we obtain the following code:

#include <vector>
#include <algorithm>

int count_even(const std::vector<int, std::allocator<int> > & v)
{
    
  class __lambda_6_27
  {
    public: 
    inline /*constexpr */ bool operator()(int i) const
    {
      return (i % 2) == 0;
    }
    
    using retType_6_27 = auto (*)(int) -> bool;
    inline /*constexpr */ operator retType_6_27 () const noexcept
    {
      return __invoke;
    };
    
    private: 
    static inline bool __invoke(int i)
    {
      return (i % 2) == 0;
    }
    
    public: 
    // inline /*constexpr */ __lambda_6_27(const __lambda_6_27 &) noexcept = default;
    // inline /*constexpr */ __lambda_6_27(__lambda_6_27 &&) noexcept = default;
    // /*constexpr */ __lambda_6_27() = default;
    
  };
  
  const __lambda_6_27 & my_func = __lambda_6_27{};
  return static_cast<int>(std::count_if(std::cbegin(v), std::cend(v), __lambda_6_27(my_func)));
}

As you might have guessed, a lambda function is in fact a functor (here called __lambda_6_27). Thus, the assignation calls the constructor of that functor, which is a rvalue.

We just saw that we could bind a rvalue to a const ref, thus binding a lambda to a const ref is legal.

This is why we can bind a lambda to a const ref.

Performance and optimization

To answer the question that if we should bind a lambda to a const ref instead of a value, we have to evaluate if one method is faster that the other.

Execution time

I’ll use Quick C++ Benchmarks (quick-bench.com) to evaluate execution time.

Here are the snippets I’ll use:

std::vector<int> v = {0,1,2,3,4};

static void ConstRef(benchmark::State& state)
{
  const auto& l = [](int i)->bool{ return i%2 == 0;};
  for (auto _ : state)
  {
    std::count_if(cbegin(v), cend(v), l);
  }
}
BENCHMARK(ConstRef);

static void Plain(benchmark::State& state)
{
  auto l = [](int i)->bool{ return i%2 == 0;};
  for (auto _ : state)
  {
    std::count_if(cbegin(v), cend(v), l);
  }
}
BENCHMARK(Plain);

I ran benchmarks using Clang 11.0 and GCC 10.2, with all optimization options between -O0 and -O3.

And here are the results:

CompilerOptiomization
option
ET with
const ref
ET with
plain value
const ref / plain
value ratio
Clang 11.0-O032.30633.7320.958
Clang 11.0 -O1224.96204.921.097
Clang 11.0 -O23.9982e-64.0088e-60.997
Clang 11.0 -O33.7273e-64.1281e-60.903
GCC 10.2-O0 64.37965.0170.990
GCC 10.2 -O1 11.75411.8710.990
GCC 10.2 -O2 3.7470e-64.0196e-60.932
GCC 10.2 -O3 3.6523e-63.9021e-60.936

What you want to look at is the last column, which computes the ratio between const-ref and plain value (the units of the execution times have unlabeled units, it’s useless to try and compare them directly).

A value greater that 1 means the const-ref version is slower than the plain version, while a value lower that 1 means the const-ref is slower.

Here are the charts:

All in all we can see that while the const-ref version is still faster than the value version, the difference doesn’t go above 10%. The highest differences are at the maximum optimization level.

If the code in a bottleneck (in the 20% of Pareto’s law) then this 10% can make a little difference, but I wouldn’t expect more than one or two percent gain overall (including other parts of your code).

However, if this is not a bottleneck, then there’s no version to prefer over the other one.

Compilation time

Does the const ref binding can affect compilation time? To answer this, I used C++ Build Benchmarks (build-bench.com).

I compiled the following codes in the same circumstances, using Clang 11.0 and GCC 10.2, with all optimization options between -O0 and -O3:

#include <vector>

int main() 
{
    const auto& l = [](int i)->bool{ return i%2 == 0;};
    std::vector<int> v= {0,1,2,3,4};
    std::count_if(cbegin(v), cend(v), l);
}

Then, without using const ref:

#include <vector>

int main() 
{
    auto l = [](int i)->bool{ return i%2 == 0;};
    std::vector<int> v= {0,1,2,3,4};
    std::count_if(cbegin(v), cend(v), l);
}

And here are the results:

CompilerOptimization
option
BT with
const ref
BT with
plain value
const ref / plain
value ratio
Clang 11.0-O00.36290.35101.034
Clang 11.0 -O10.40100.40350.994
Clang 11.0 -O20.37550.37650.997
Clang 11.0 -O30.37450.37351.003
GCC 10.2 -O0 0.39150.39001.004
GCC 10.2 -O1 0.38300.38101.005
GCC 10.2 -O2 0.37650.37750.997
GCC 10.2 -O3 0.37650.37501.004

In any case, there is less than 4% difference between the two versions, and in most cases it’s event under 1%. We can say that there is no effective difference.

Conclusion

There is not real advantage in binding a rvalue to a const ref instead of a plain value. Most of the time, you’ll prefer to use a const value just for the sake of saving a symbol (but there is no huge difference).

In case you are in a bottleneck, you might consider using const refs instead of values, but I suggest you do your own benchmarks, tied to your specific context, in order to detemine which version is the better.

Thanks for reading, and see you next week!

Author: Chloé Lourseyre

Yet another pamphlet about inlining

Author: Chloé Lourseyre

Introduction

This week’s topic will be about the inline keyword.

This is a very old subject that, for me, has been settled for years. And everytime I re-enter the subject and document myself about, I end up with the same conclusions.

However, I very often read code that misuse this keyword, ending up in counterproductive and risky situations.

I will try here to summarize all you need to remember about inlining, giving pros, cons and situations to use it and to not use it.

Definition

I’ll start with the academical definition of inline :

The original intent of the inline keyword was to serve as an indicator to the optimizer that inline substitution of a function is preferred over function call, that is, instead of executing the function call CPU instruction to transfer control to the function body, a copy of the function body is executed without generating the call. This avoids overhead created by the function call (passing the arguments and retrieving the result) but it may result in a larger executable as the code for the function has to be repeated multiple times.
Since this meaning of the keyword inline is non-binding, compilers are free to use inline substitution for any function that's not marked inline, and are free to generate function calls to any function marked inline. Those optimization choices do not change the rules regarding multiple definitions and shared statics listed above.

Source : inline specifier – cppreference.com

What you need to retain from that are two things :

  • inline is used to avoid function overhead, replacing the call by the implementation at compile time.
  • It is only a hint given to the compiler. Some function declared inline may not really be inlined, and vice-versa.

With that in mind, let’s take a tour of the pros and the cons of this feature.

Pros

First of all, even if it’s a hint, the keyword may influence the compiler. It may not, but it also may. We can for instance think of the stupidest compiler that will just strictly respect the inline keyword, without trying to optimize himself. This is not against the standard (according to the C++11 standard §12.1.5) and the keyword is useful in this case.

According to What is C++ inline functions – C++ Articles (cplusplus.com), the pros are :

  1. It speeds up your program by avoiding function calling overhead.
  2. It save overhead of variables push/pop on the stack, when function calling happens.
  3. It save overhead of return call from a function.
  4. It increases locality of reference by utilizing instruction cache.
  5. By marking it as inline, you can put a function definition in a header file (i.e. it can be included in multiple compilation unit, without the linker complaining).

Point number 1, 2 and 3 are overally the main benefits of this feature, and the original goal of the introduction of this keyword. Those who know a bit of assembly know that pushing a lot of parameters on the stack to call function can cost more instructions that the function holds.

Point number 4 seems to be a non-negligible side-benefit of this method, but since instruction cache is far from being my specialty, I will nt develop on this.

Point number 5 is pro only in certain specific cases, but a pro nonetheless.

Cons

According to What is C++ inline functions – C++ Articles (cplusplus.com), the cons are :

  1. It increases the executable size due to code expansion.
  2. C++ inlining is resolved at compile time. Which means if you change the code of the inlined function, you would need to recompile all the code using it to make sure it will be updated
  3. When used in a header, it makes your header file larger with information which users don’t care.
  4. As mentioned above it increases the executable size, which may cause thrashing in memory. More number of page fault bringing down your program performance.
  5. Sometimes not useful for example in embedded system where large executable size is not preferred at all due to memory constraints.

Points 1 and 4, which are unknown to a lot of developpers, are the main reason that function inlining can decrease performance. It is important to remember that when using this feature.

Point 2 can be a major inconvenient, depending on your project, but don’t occur so often in my experience.

Point 3 is, in my opinion, the main con of this feature. In order to have maintainable code you need to have to be clear and organized. Inlining is a huge code smells in that regard.

Point 5 is about specific projects, so I will not develop further. But keep in mind that if you have memory constraints, inlining may have consequences.

Conlusion : when to use the inline keyword ?

Avoiding function overhead is only usefull if you are in a performance critical part of your code.

You probably already know the Pareto’s law : “80% of the execution happens in 20% of the code”. This means that the program spends most of it’s time in bottlenecks. Thus, if you inline code that is not within a bottleneck, this will have little to no effect on your program performance, while increasing its size.

What urged me to write this article is that during my coder’s life, I saw many many codebase polluted by useless inlines. I can safely say that more than 95% of the inlining I saw in industrial projects was used in non-critical code.

There is absolutly no need in decreasing the readability of the code and increasing the executable size for that end.

Here is my advice : don’t use inline unless you are 100% sure it is called inside a bottleneck.

This is the only way to be both efficient and clean.

Another type of inlining

I will end this article by adding a word about a special case of “inlining”.

If you are someone who likes to write things like this :

inline void setValue(int i) { m_value = i; }

and by that I mean, the prototype and the implementation all on one line, please understand that this prevent many debuggers from doing their jobs properly.

For instance, in VisualStudio, if you put a breakpoint inside this setValue method, when it hits the debugger won’t be able to give you the value of m_value.

So please, I’m begging you, stop doing that. It won’t cost you much to add a newline.

Thank you, and see you next week!

Author: Chloé Lourseyre

windows.h breaks the standard library (and my will to live)

Author: Chloé Lourseyre

While working on not-so-old code, I ended up with a strange compilation error in MS Visual Studio :

Here was the incriminated code :

//...

const T_LongType BIG_OFFSET = std::numeric_limits<T_LongType>::max() / 2;

//...

And here the errors :

1>MyFile.cpp(42): error C2589: '(' : illegal token on the right side of '::'
1>MyFile.cpp(42): error C2059: syntax error : '::'
1>MyFile.cpp(42): error C2059: syntax error : ')'
1>MyFile.cpp(42): error C2059: syntax error : ')'

It came with a bunch of warnings I won’t even bother writing here.

I took me a hella lot of time to find out what was wrong. The type T_LongType was correctly defined (actually a typedef of long) and I didn’t forget to include <limits>.

Many of you may already know the culprit, and it’s the following line :

#include <windows.h>

Indeed, if we look into the code of this library, we can see a surprising piece of code :

#ifndef NOMINMAX

#ifndef max
#define max(a,b)            (((a) > (b)) ? (a) : (b))

#ifndef min
#define min(a,b)            (((a) < (b)) ? (a) : (b))

#endif  /* NOMINMAX */

To my defense, the file in which the error occured did not include windows.h. It was included by another header.

Explanation

The fact that windows.h defines macros called min and max implies that, during the preprocessor phase of the compilation, every instances of the words min and max will be replaced.

This means that, after the preprocessing, instead of seeing this :

const T_LongType BIG_OFFSET = std::numeric_limits<T_LongType>::max() / 2;

The compiler will see this :

const T_LongType BIG_OFFSET = std::numeric_limits<T_LongType>::(((a) > (b)) ? (a) : (b))() / 2;

Which makes no sense, thus the compilation errors mentioned above.

Many reasons to not include windows.h

Here is a non-exhaustive list that why it is a bad practice to include this header :

  • It breaks the standard library just by including it. The way we use the functionality of the standard library should work whatever the header files we include.
  • It forces you to define NOMINMAX and the beginning of each headers that include windows.h. And if you ever happen to forget this, every file that will include your header will have to define it.
  • Since it’s OS-dependant, it’s better to avoid it as long as you can. If you use where you don’t need to, you won’t be able to port your code to other systems, you may obtain bad coding habits (by relying too much on it) and to not forget that the more specific a library is, the less maintained it will be.

Conclusion

All in all there are ways to use windows.h safely, but you must be absolutely be solid in how you include it to avoid side-effects to other sources and headers.

As long as you can, don’t use it.

Author: Chloé Lourseyre