Skip to content

File and Module Structure

amirroth edited this page Sep 22, 2021 · 10 revisions

File Extensions

  • C++ source file should be named .cpp or .cc NOT .c
  • C++ header files should be named .hpp or .hh NOT .h

Include Guards In Header Files

Header files must contain a distinctly-named "include guard" to avoid problems with including the same header multiple times or conflicting with other headers from other projects.

#ifndef MYPROJECT_MYMODULE_HPP
#define MYPROEJCT_MYMODULE_HPP

namespace MyProject {

class MyClass {
};

}

#endif // MYPROJECT_MYMODULE_HPP 

It's good hygiene to add a comment at the end of #endif lines to document which #ifdef it is closing.

Use "" For Including Local Files

... <> is reserved for system includes.

// Bad Idea. Requires extra -I directives to the compiler and goes against standards
#include <string>
#include <includes/MyHeader.hpp>

// Worse Idea
// requires potentially even more specific -I directives and makes code more difficult to package and distribute 
#include <string>
#include <MyHeader.hpp>

// Good Idea
// requires no extra params and notifies the user that the file is a local file
#include <string>
#include "MyHeader.hpp"

[SM] The reason for <path/MyHeader.hpp> is two-fold:

  • Search semantics for "MyHeader.hpp" are not fully specified in the language and thus create a potential portability problem. Search with <> is precisely specified. This is particularly important for cross-platform projects where you may want precise override control to pick up different headers depending on the platform/compiler (for a cleaner more maintainable approach than an #ifdef mess).
  • Putting a namespace path in front of the header file name prevents header name collisions between the application and a library or 2 application namespaces.
  • These reasons are why projects like Boost use the <boost/header.hpp> style.

[JT] Having just grepped through the boost source: there is a surprising amount of inconsistency within boost. Practically speaking, I have never seen a portability issue with using "" for includes, and have seen it solve far more problems then it has created. However if we do use the <> includes as you suggest, we need to be consistent and use fully scoped paths, like boost does, (real world example) boost/log/expressions/predicates.hpp:#include <boost/log/expressions/predicates/channel_severity_filter.hpp>

Including Header Files in Other Header Files

Including header files from other header files increases compilation time and is considered bad practice. Better practice is to include header files in code files in "topological order" such that header files are always included after the header files that they themselves need.

There is another good way to minimize the inclusion of header files from other files. If you only need to reference a type that is defined in another header file, i.e., you are declaring a function that has a reference or pointer parameter of that type, then you can simply "forward declare" that type without including the header file itself. If you are only using a pointer or a reference to a type and never using it by value or looking into it, then the compiler doesn't actually need to know the definition of the type, e.g., its size.

For example, EnergyPlus now passes a struct EnergyPlusData & state parameter to essentially every function and method call. It would stand to reason therefore that every header file needs to include Data/EnergyPlusData.hh where the struct EnergyPlusData is defined. However, this is sufficient.

struct EnergyPlusData; // No need to include "Data/EnergyPlusData.hh" if all you are doing is using EnergyPlusData it as a reference argument

void myFunction(struct EnergyPlusData & state);

Note, this does not work if you actually have to include an instance of EnergyPlusData as a field in another type, if you have to create a variable of type EnergyPlusData, or if EnergyPlusData were a template. If any of these were the case, the compiler would have to know the size and structure of EnergyPlusData and you would have to include the Data/EnergyPlusData.hh header in this header.

Namespaces and Namespace Resolution

EnergyPlus has a namespace construct which helps in large projects by de-conflicting type and variable names that may recur in different modules. Namespaces are commonly used in libraries for the same reason. The C++ standard library uses the namespace std which is why its types have to be resolved with a std::, e.g., std::string, std::array, etc. EnergyPlus defines a project-wide EnergyPlus namespace because it itself can can be used as a library by external programs.

Namespaces nest and it's a good idea to have sub-namespaces within EnergyPlus to avoid conflicts across modules. There is no fixed one-to-one mapping between namespaces and files. A file can contain multiple namespaces and a namespace may encompass multiple files. Namespaces are logical entities that should encompass logical collections of definitions, data, and functionality. Here is an example, first the .hh file:

// Psychrometrics.hh
#ifndef PSYCHROMETRICS_HH
#define PSYCHROMETRICS_HH

namespace EnergyPlus {
namespace Psychrometrics {
   ...
} // namespace Psychrometrics
} // namespace EnergyPlus

#endif // PSYCHROMETRICS_HH

And now the .cc file.

// Psychrometrics.cc
#include "Psychrometrics.hh"

namespace EnergyPlus {
namespace Psychrometrics {
   ...
} // namespace Psychrometrics
} // namespace EnergyPlus

Note, it's also good hygiene to add a comment to the closing brace of a namespace to indicate what you are closing.

An important thing about namespace names is that they should be short. Code would be much less readable if the C++ standard library namespace was named CppStandardLibrary and all strings had to be defined as CppStandardLibary::string. Explicit name resolution can be avoided within a translation unit (i.e., a file or a function) using the using statement:

using std;
string myString;

However, this is also frowned upon because it hides the origins of types, variables, and functions from other namespaces and removes a layer of documentation that the namespace name provides. You should certainly not use using in header (.hh) files and you should migrate away from this practice in code (.cc) files too.

A better approach is to use short namespace names and to remove naming redundancy between the namespace and types and functions within it. For instances Pyschrometrics is not a good namespace name, a better name is Psy or maybe Psych. Given the namespace name Psy and avoidance of using, the names of the psychrometric functions themselves are also redundant. There is no need to write Psy::PsyTsatFnPb(Pb), Psy::TsatFnPb(Pb) is sufficient. This is the same pattern one should follow with enum class, each of which is its own sub-namespace. There is no need to repeat the name of the enum class in each of the enumerated values since the name of the enum class in needed for name resolution anyway.

Note, if Psychrometrics is a bad namepsace name, then names likes ZoneContaminantPredictorCorrector and HeatPumpWaterToWaterHEATING are even worse!

Clone this wiki locally