Lucky Resistor
Menu
  • Home
  • Learn
    • Learn C++
    • Product Photography for Electronics
      • Required Equipment and Software
    • Soldering for Show
  • Projects
  • Libraries
  • Applications
  • Shop
  • About
    • About Me
    • Contact
    • Stay Informed
  •  
Menu

It’s Time to Use #pragma once

Posted on 2019-07-132022-09-04 by Lucky Resistor

In my opinion, preprocessor macros and the outdated #include mechanism are one of the worst parts of the C++ language. It is not just these things are causing a lot of problems, even more, it is very time consuming to find them.

This article will focus on #pragma once. In the past, I already wrote articles about how to avoid macros and why you should use namespaces.

While I usually focus on embedded development on this blog, this topic especially aplies to desktop software. It is valid not only for C++, but also C programs.

As usual, I try to cover the topic in detail to bridge any knowledge gaps you may have.

What is the Problem with Macro Header Guards?

Macro based header guards can lead to unexpected problems. In the following sections, I will explain the core concepts and demonstrate the problem using a simple example.

Compiling Units

In C and C++ a compile unit usually consists of a header (.h or .hpp) and implementation file (.c or .cpp). It is the lowest level of encapsulation. The header file contains the interface of the unit, while the cpp or c file contains the implementation.

The idea behind this concept is to allow “hiding” or encapsulating implementation details in a file. After the unit is compiled, you get an object file .o or with several ones a complete library.

The user of the compiling unit or library does not have to know the code of the implementation file and uses the header to access the interface for it.

The Stream Example

The following example is a minimal program with two compile units. For the Stream class header, old-style header guards are used.

Stream.hpp (With Old Guards)

#ifndef STREAM_H
#define STREAM_H

class Stream {
public:
    void flow();
};

#endif

Stream.cpp

#include "Stream.hpp"

#include <iostream>

void Stream::flow()
{
    std::cout << "murmur..." << std::endl;
}

main.cpp

#include "Stream.hpp"

int main()
{
    Stream stream;
    stream.flow();
    return 0;
}

How the Example Compiles

Most software developers use comfortable IDE and therefore have normally no contact with the actual invocation of the compiler commands. If the stream example is compiled using the GNU compiler, the following commands are executed:

c++ -o Stream.o Stream.cpp
c++ -o main.o main.cpp
c++ -o myApp Stream.o main.o

First, the unit Stream.cpp is compiled into Stream.o. Next, the unit main.cpp is compiled into main.o, and eventually the two compiled .o units are linked into the final application.

Please note, these processes are a simplification, so you can follow the topic more easily. It basically works in the shown way. Modern compiler introduces features like precompiled headers or link-time code generation which blur the borders between the processes a little bit.

The Preprocessor

If a unit is compiled, the code of the unit is processed by the preprocessor. It will include all the required files. resolve defined macros and put everything in one single file for the actual compiler.

Compilers usually support an option where you can get the immediate output of the preprocessor, without actually compiling the unit.

c++ -E -o main-pre.cpp main.cpp

For the main unit, this will generate a file like the following one:

# 1 "main.cpp"
# 1 "Stream.hpp" 1

class Stream {
public:
    void flow();
};
# 2 "main.cpp" 2

int main()
{
    Stream stream;
    stream.flow();
    return 0;
}

The preprocessor marked all processed and included files with # comment lines. As you can see, the macro header guard is gone, because all these preprocessor statements were resolved.

First, it started with the main.cpp file then found the #include "Stream.hpp" which it included at this point. Afterwards, it continued the with the rest of the main.cpp file.

It is a very simple example and therefore easy to read and understand. If you are working with libraries which are using hundreds of included files, the preprocessor result can be overwhelming long and hard to read.

Still, this is more or less what the compiler will process, in exact this order. Even modern compiler do not exactly process the files in this way, creating an immediate preprocessor file can help if you are searching for compilation issues.

Why is a Header Guard Required?

The guard is required to prevent a header file included twice in the same compiling unit. Have a look at the following dependency graph:

From the file main.cpp the two header files Town.hpp and Forest.hpp are included. Both files include Stream.hpp.

For the preprocessor, the includes look as shown above. As you can see, the file Stream.hpp is included twice.

If the preprocessor creates the compile unit, without header guards, the result will look as shown in the following example:

As you can see, the declaration class Stream is made twice, which will generate an error. The compiler does not know the reason for the second declaration and has no choice to stop at this point.

A header guard will detect the header Stream.hpp was already included before and omit its contents for the second #include.

Using a Library

The example program is working fine, and for the next iteration, we like to replace the <iostream> of the standard library with the advanced text stream library magic_text_stream. An inexperienced software developer developed it, and it looks like this:

magic_text_stream/ConsoleStream.hpp

#ifndef CONSOLE_STREAM_H
#define CONSOLE_STREAM_H

#include "Stream.hpp"

namespace magic_text_stream {

class ConsoleStream : public Stream {
public:
    void write(const std::string &text) override;
};

}

#endif

magic_text_stream/ConsoleStream.cpp

#include "ConsoleStream.hpp"

#include <iostream>

namespace magic_text_stream {

void ConsoleStream::write(const std::string &text)
{
    std::cout << "Console: " << text << std::endl;
}

}

magic_text_stream/Stream.cpp

#ifndef STREAM_H
#define STREAM_H

#include <string>

namespace magic_text_stream {

class Stream
{
public:
    virtual void write(const std::string &text) = 0;
};

}

#endif

We assume for the example, the library comes precompiled, and we get a static library and the header files installed on the system.

Adding the Library to the Project

To add the new magic_text_stream library to our project, we include the header in our implementation file. The library uses a namespace, and the class ConsoleStream also does not conflict with our Stream class. Therefore everything seems fine if we replace <iostream> as shown below:

Stream.cpp

#include "Stream.hpp"

#include <magic_text_stream/ConsoleStream.hpp>

void Stream::flow()
{
    magic_text_stream::ConsoleStream console;
    console.write("murmur...");
}

Strange Compilation Errors

If we try to compile our project, we get a really strange error:

In file included from Stream.cpp:3:
magic_text_stream/ConsoleStream.hpp:10:22: error: use of undeclared identifier 'std'
    void write(const std::string &text) override;

So, the identifier std is not declared? We see ConsoleStream.hpp includes Stream.hpp, which correctly includes <string>, therefore the namespace std should be known.

As you already noticed, the error is caused because the library and the application both using the STREAM_H macro as header guard.

Mistakes were Made

Clearly, mistakes were made. The library author should have used a header guard macro, as shown below:

#ifndef MAGIC_TEXT_STREAM_STREAM_HPP
#define MAGIC_TEXT_STREAM_STREAM_HPP

// ...

#endif

Also, the author of the application should prefix the header guards with the application name.

While fixing the problem is relatively simple, many beginners are entirely lost with this kind of compilation errors. The displayed error is completely unrelated to the actual problem. Without experience, it is hard to find and will waste hours or even days.

A Long List of Potential Problems

Using macro header guards has the potential of a long list of problems. While reviewing code, I found all of the described ones at least once.

Other than problems in the code itself, there is no compiler warning about a macro header guard which does not match the filename and namespace.

Below, the top four problems with macro header guards.

1. The name of the header guard lacks a prefix with the name of the library or namespace.

#ifndef STREAM_H
#define STREAM_H  <--- missing prefix
// ...
#endif

2. The name was not changed after changing the name of the file, class or namespace.

#ifndef APP_STREAM_H
#define APP_STREAM_H  <--- does not match file/class

class House {
// ...
};

#endif

3. The name used with #ifndef does not match the one used with #define.

#ifndef MAGIC_TEXT_STREAM_STREAM_HPP
#define MAGIC_TEXT_STREAN_STREAM_HPP

// ... found it?

#endif

4. The #endif statement is missing.

#ifndef MAGIC_TEXT_STREAM_STREAM_HPP
#define MAGIC_TEXT_STREAM_STREAM_HPP

// ...

The Simple Solution

Using #pragma once will delegate the task, of detecting subsequent #include statements for the same file, to the compiler. It can do this efficiently and safely. As a user, there is no room for mistakes. Just place the directive in the first line of every header file.

Stream.hpp

#pragma once

class Stream {
public:
    void flow();
};

There is no unique name you have to write correctly and no #endif which must exist at the end of the file. Also, if you change the name of the file or class, no changes are required.

Why is this not Part of the C++ Standard?

All #pragma statements are meant to be compiler-specific instructions which are per definition not portable. Therefore using #pragma once is not guaranteed to be implemented by a compiler and also, there is no standardised way how duplicate includes are detected.

Detecting duplicate includes is not always as simple as it seems: If a project uses symlinks to include libraries, the same header file may be accessed using two different absolute paths. Also, compiling has not necessarily be done on the same computer; it can be split up to many processes and executed on different servers on the network.

For these reasons, the first accepted proposal for a #pragma once replacement was not made until 2016 with the number P0538R0. It suggests the new preprocessor directives #once and #forget as a qualified replacement.

Also, C++20 will most likely include a new concept of modules, which will solve the current problems with included header files.

At the time I write this it is the year 2019, and there are still compiler which do not fully implement the C++11 standard, which is eight years old. So, do not expect to have modules or the #once statement until 2028.

Pragma Once Support Today

The #pragma once statement is supported by almost all compiler suites, even most for embedded development. You can safely use it and expect it will work with any compiler.

A Wikipedia page will give you a list of compiler and their support of the statement.

Conclusion

I recommend using #pragma once instead of old macro-based header guards. It will reduce the number of preprocessor macros and prevent potential and hard to find compilation problems. You should also replace existing macro-based header guards with this statement if you do maintenance work on existing code.

The statement is supported by all relevant compiler suites in a very efficient and safe way. While there may be rare problem cases, in distributed compiling environments, this will affect just a few users who are well aware of this.

I hope this article gave you some motivation to get rid of your old macro-based header guards. If you are not sure if #pragma once will work for your project, replace the old guards using a script and try to compile it.

If you have questions, miss some information or have any feedback, feel free to add a comment below.

Learn More

Auto and Structured Binding

Auto and Structured Binding

This article is just a short follow-up article for “Write Less Code using the ‘auto’ Keyword”. Structured binding is something handy, introduced in C++17. Therefore, only the latest compiler will support it. If you are ...
Read More
How and Why to Avoid Preprocessor Macros

How and Why to Avoid Preprocessor Macros

While most naming conflicts in C++ can be solved using namespaces, this is not true for preprocessor macros. Macros cannot be put into namespaces. If you try to declare a new class called Stream, but ...
Read More
C++ Templates for Embedded Code (Part 2)

C++ Templates for Embedded Code (Part 2)

Templates are a powerful feature of the C++ language, but their syntax can be complex. Here I will continue with the second part of the article. Although the examples I provide are for the Arduino ...
Read More
Event-based Firmware (Part 1/2)

Event-based Firmware (Part 1/2)

You start with small, simple firmware. But with each added feature, the complexity grows and grows. Soon, you need a good design in order to maintain the firmware and ensure the code remains clean and ...
Read More
Use Enum with More Class!

Use Enum with More Class!

You may be familiar with enum values, but do you know about enum classes? This great feature was introduced with C++11 to solve several problems with the regular enum declaration. Now I will explain the ...
Read More
How and Why to use Namespaces

How and Why to use Namespaces

Namespaces are a feature of C++ which address the problem of name conflicts. There is a "global" namespace, where everything lives which was declared without namespace. Especially the Arduino environment declares a huge amount of ...
Read More

Leave a Reply Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Stay Updated

Join me on Mastodon!

Top Posts & Pages

  • Storage Boxes System for 3D Print
  • Build a 3D Printer Enclosure
  • Event-based Firmware (Part 1/2)
  • Yet Another Filament Filter
  • Circle Pattern Generator
  • Circle Pattern Generator
  • Projects
  • It's Time to Use #pragma once
  • Real Time Counter and Integer Overflow
  • Units of Measurements for Safe C++ Variables

Latest Posts

  • Better Bridging with Slicer Guides2023-02-04
  • Stronger 3D Printed Parts with Vertical Perimeter Linking2023-02-02
  • Logic Gates Puzzle 1012023-02-02
  • Candlelight Emulation – Complexity with Layering2023-02-01
  • Three Ways to Integrate LED Light Into the Modular Lantern2023-01-29
  • The 3D Printed Modular Lantern2023-01-17
  • Rail Grid Alternatives and More Interesting Updates2022-12-09
  • Large Update to the Circle Pattern Generator2022-11-10

Categories

  • 3D Printing
  • Build
  • Common
  • Fail
  • Fun
  • Learn
  • Projects
  • Puzzle
  • Recommendations
  • Request for Comments
  • Review
  • Software
Copyright (c)2022 by Lucky Resistor. All rights reserved.
 

Loading Comments...