Przejdź do głównej zawartości

Tidy #includes

Tidy #includes

Having tidy includes in C++ project is often thought as low priority. It can however help you with making your code less fragile. This is doubly important when your project is a library that will be used by other people.
I propose that "good" includes should follow those rules:
  1. Associated header (foo.hpp for foo.cpp) always comes first
  2. Any file should have all the headers it needs.
  3. System headers are in <angle_brackets>, project headers are in "quotes"
  4. Project headers are first, STL headers are last, between them are libraries in order of decreasing locality.
  5. Use #pagma once instead of include guards
  6. Within groups decided by 4), alphabetic order is nice to have. 

Associated header is first

The header that declares class / methods should be the first to include. It also should have the same file name as current source file, except for extension. Some tools depend on this - for example IDEs use this to implement "go to declaration/implementation" feature.

Include everything that is needed

...and nothing that is not needed.

cpp files

.cpp should have all the includes that are needed to compile. Generally less - the better, although sometimes redundancy helps during code changes.

Headers

Header files files are more tricky. You have to decide what to include and what to forward-declare. You will be safe if you do #include for every type used in declarations. You can replace some of them with forward-declarations and gain faster recompile time when some popular header changes ("popular" here means "needed by many files"). Not all substitutions are possible - see this SO question for brief explanation. And not all are useful - finally, somewhere, #include will be needed anyway. When you have to many forward-declarations you will have hard time supplying missing #includes later on.
So as many other problems in C++, declare-or-include is your implementation decision.
Remember that the fact that project compiles does not mean you have all necessary headers. See this example:
// foo.hpp:
class Foo {};
// bar.hpp:
class Bar {
    Foo foo;
};
// main.cpp:
#include "foo.hpp"
#include "bar.hpp"
int main() {
    Foo foo;
    Bar bar;
    // use foo and bar... 
    return 0;
} 
Here bar.hpp needs foo.hpp, but code compiles and works as intended. But if you change order of includes in main.cpp or decide that main() doesn't need to use foo, code breaks:
// main.cpp:
#include "bar.hpp"
int main() {
    Bar bar;
    // does not compile - Foo is unknown type
    return 0;
}
Compiler didn't warn us earlier because by chance foo.hpp was included before bar.hpp and provided Foo definition.For this reason - if bar.hpp uses Foo, it should include foo.hpp.

"quotes" for project headers, <brackets> for system headers

There is minor difference between #include "foo" and #include <foo>. When using "quotes", compiler searches for the file in local directory first. Only when file is not found, it is searched for in system include directories. <angle brackets> go directly to system headers (relevant quote from standard). Sometimes you can even get a warning for using bad format, but I don't think I ever got compilation error. This difference is minor, but following the rule I suggest, you make it easier for your fellow programmer to read your code - s/he will know instantly whether given include is part of project or some external dependency.

System headers should be after local ones

Headers should be in order from most local to most global. It gives a measure of protection from problem in paragraph above. Just replace Foo class with std::string:
// bar.hpp:
class Bar {
    std::string str;
};
// main.cpp:
#include <string>
#include "bar.hpp"
int main() {
    std::string str;
    Bar bar;
    // use str and bar...
    return 0;
}
Again code compiles and hides missing include in bar.hpp.
However if we followed suggested order of includes, compiler immediatelly would give error. Its always better to fail early - preferably before releasing your bar.hpp library to the public :-)

Reversed approach

Some people say it's better to sort includes other way around. It then protects as form bad libraries overriding standard classes. For example bar.hpp could define std::string class. By including <string> first we are protected from accidentally defining standard classes. However proper use of namespaces gives us enough protection. Simply use only namespace names that you own.
For more discussion see this SO question.

 #pragma once is better than include guards

When writing a header you should always use #pragma once or include guards. Either of them saves you from having single file conflicting with itself when included in different places. Which one is better is a bit controversial matter.
// with_pragma.hpp:
#pragma once
class Foo {
...
};

// with_include_guards.hpp:
#ifndef _WITH_INCLUDE_GUARDS_HPP_
#define _WITH_INCLUDE_GUARDS_HPP_
class Bar {
...
};
#endif // _WITH_INCLUDE_GUARDS_HPP_
Pragma once is widespread compiler extension and include guards are backed by standard. So if you happen to use nonsupporting compiler you have no choice.
Also if you have strange build system, pragma once may fail you. If single file (foo.h) is accessible through more than one path (remote mounts, symlinks), bad thing may happen. Those however are edge cases that happen only with insane build systems.
Having cons out of the way, it is easier to use #pragma once. You have only 1 short line to write instead of three. You don't have to update it when changing name of file. You won't by mistake mismatch define and ifndef (define something else that what was checked for). When you create new file by copying contents of other similar one, you won't forget to update name in guards.
Personally I think that include guards are much more likely to cause you trouble than pragma once.

Alphabetic sort of headers

This is the least important rule that I propose. It doesn't provide any added safety, but helps you quickly determine whether given header is included in current file. If you have many includes, looking through them all would be tedious - better to have them sorted and do binary search ;)
However I know that it would be tedious to maintain the order when adding new includes - especially when you add them by copy-pasting whole block from other file. Luckily alphabetic sort is nice byproduct of using clang-tidy that I will introduce in next article.

Komentarze

Popularne posty z tego bloga

Cleaning up #includes

Cleaning up includes In previous article I described why you should care about clean #includes and what rules I suggest to achieve it. If you already have a project that doesn't follow those rules, it would be tedious manual task to implement my advises. This article describes how some tools can help you answer these questions:
How to replace include guards with pragma once in whole project?How to sort includes in whole project?How to remove redundant includes and add missing ones?How to replace angle bracket includes with quotes only for project headers? #pragma once with include-guard-convert.pyInclude-guard-convert is simple tool that will find all include guards and change them to #pragma once. Tool doesn't need installation, simply download script from github and run it on your header files:
wget https://raw.githubusercontent.com/marcusmueller/include-guard-convert/master/include-guard-convert.py chmod +x include-guard-convert.py ./include-guard-convert.py proj_path/**/*.…

GDB - beyond basics

GDB is console debugger that every Linux-using programmer heard about. It is not however easy to learn. Greg Law in his CppCon talk presented some of obscure, but useful features.

Text user interface Normally we use GDB with command line interface (CI). Beyond this, GDB has TUI based on Curses library. To activate it, use keyboard shortcut ctrl-x-a (hold ctrl, press x, unpress x, press a). Now you can see the code as you go through it.
ctrl-x-a - activate/deactivate TUIctrl-l - when screen gets messed up, use it to redraw. Happens when program prints to stdout/stderrctrl-p / ctrl-n - since you can't use arrows to reuse previously written command, use ctr-p/n instead of arrow up/down
ctrl-f / ctrl-n are arrows left / right
ctrl-a / ctrl-e are home / end (all those are copied from Emacs)ctrl-x-2 - second window (assembly). Shell You can run shell commands inside GDB command line. Just use keyword "shell" at the beginning. Examples:
shell psshell cat temporary_file.txtshell k…