KAHIBARO
Discord Login Register

23.3. Using Header Files

Why Use Header Files in ROOT Analyses

Using header files in ROOT analyses is mainly about separating declarations from implementations, improving code organization, and making your analysis easier to maintain and reuse. When your ROOT code grows beyond a single short macro, header files become essential.

In a typical C++ ROOT project, you place function and class declarations in .h (or .hh, .hpp) files and keep the actual code in .C or .cc files. ROOT can interpret and compile both, so the structure is the same as in standard C++.

Rule: Put interfaces (function declarations, class definitions, configuration structures) in header files, and put implementations (function bodies, analysis logic) in source files.

This separation helps ROOT’s C++ interpreter and compiler understand your code, avoids repeated declarations, and gives you a cleaner project layout.

Basic Structure: Headers and Source Files

A minimal ROOT analysis that uses header files usually contains at least three categories of files:

  1. One or more header files, for example Analysis.h, Histograms.h, Cuts.h.
  2. One or more source files with implementations, for example Analysis.C, Histograms.C.
  3. A main macro or main function that calls your analysis, for example runAnalysis.C.

A very simple example looks like this:

Analysis.h:

cpp
#ifndef ANALYSIS_H
#define ANALYSIS_H
void RunMyAnalysis(const char *inputFile, const char *outputFile);
#endif

Analysis.C:

cpp
#include "Analysis.h"
#include "TFile.h"
#include "TH1F.h"
void RunMyAnalysis(const char *inputFile, const char *outputFile) {
    // analysis code here
}

runAnalysis.C:

cpp
#include "Analysis.h"
void runAnalysis() {
    RunMyAnalysis("data.root", "result.root");
}

You then execute in ROOT:

cpp
.x runAnalysis.C

ROOT will read runAnalysis.C, see the #include "Analysis.h", and load the declaration of RunMyAnalysis. When ROOT parses or compiles Analysis.C, it also includes Analysis.h, so the declarations are consistent.

Important: Always include the corresponding header in every source or macro file that uses a function or class. Do not rely on implicit knowledge of function signatures.

Typical Contents of a Header File in ROOT

In a ROOT analysis, your header file usually contains:

Function declarations for reusable analysis tasks:

cpp
#ifndef HISTUTILS_H
#define HISTUTILS_H
class TH1;
void SetupStandardStyle();
TH1 *CreateEnergyHistogram(const char *name, const char *title);
#endif

Simple helper class or struct definitions that you want to share across multiple .C files:

cpp
#ifndef EVENTDATA_H
#define EVENTDATA_H
struct EventData {
    float energy;
    float time;
    int   detectorId;
};
#endif

Configuration containers and constants, for example cut values or detector constants that should be visible everywhere.

ROOT classes and functions from the ROOT libraries themselves are already declared in ROOT’s own headers, such as TH1.h, TFile.h, or TTree.h. Your custom header files extend this by declaring your own analysis interface.

Rule: Only put declarations in headers, not executable code. Do not define large functions in headers unless you know exactly why you need them inline.

Include Guards and Why They Matter

ROOT’s interpreter will encounter your headers multiple times if they are included in several .C files. Without protection, this leads to duplicate definition errors. Include guards prevent that.

A typical include guard has the form:

cpp
#ifndef ANALYSIS_H
#define ANALYSIS_H
// declarations
#endif

You should use a unique macro name. A common pattern is the uppercase file name with non-alphanumeric characters replaced by underscores.

For example:

File name | Recommended guard macro
---------|-------------------------
Analysis.h | ANALYSIS_H
MyProjectConfig.h | MYPROJECTCONFIG_H
EventData.hh | EVENTDATA_HH

ROOT’s C++ interpreter understands include guards exactly as any C++ compiler does, so the protection works both for interpreted macros and for compiled code.

If you forget include guards, you may run into errors such as “redefinition of class” or “redefinition of function” after including the same header from multiple source files.

Including ROOT Headers and Your Own Headers

When you use header files, you regularly include two kinds of headers:

  1. ROOT or standard library headers, for example:
cpp
   #include "TH1F.h"
   #include "TTree.h"
   #include <vector>
  1. Your own headers, for example:
cpp
   #include "Analysis.h"
   #include "EventData.h"

For ROOT headers, you usually use double quotes as well because they are installed in ROOT’s include path:

cpp
#include "TFile.h"
#include "TH1F.h"

For standard C++ headers, you use angle brackets:

cpp
#include <iostream>
#include <vector>

For your project headers, use double quotes, and keep them in a known location such as the project root or an include directory. A typical small analysis project may have this structure:

PathContent
analysis/Project root
analysis/include/Analysis.hPublic analysis declarations
analysis/include/EventData.hStructs and configuration
analysis/src/Analysis.CImplementation of analysis functions
analysis/src/Helpers.CExtra helper functions
analysis/runAnalysis.CEntry macro

When you run ROOT from analysis/, you can include your headers directly:

cpp
#include "include/Analysis.h"

If you often use the same include path, you can choose a shorter layout and keep headers next to their source files for very small projects.

Rule: Never include a .C file from another .C or .C-style macro. Always include the corresponding .h header instead.

Forward Declarations vs Including Headers

Sometimes you only need to mention a type without using its full definition in a header. To avoid unnecessary includes, you can use a forward declaration. This is useful when you want to reduce dependencies between headers and speed up compilation.

For example, if you only need pointers or references to ROOT classes in a header, you can write:

cpp
#ifndef MYMODULE_H
#define MYMODULE_H
class TH1;
class TTree;
void FillHistogramFromTree(TH1 *hist, TTree *tree);
#endif

In the corresponding .C file you include the full ROOT headers:

cpp
#include "MyModule.h"
#include "TH1.h"
#include "TTree.h"
void FillHistogramFromTree(TH1 *hist, TTree *tree) {
    // implementation that uses the full TH1 and TTree interface
}

Forward declarations are a C++ feature that ROOT understands. They help keep headers small and reduce the number of ROOT headers that every file must parse.

Use forward declarations when:

You only need pointers or references to a class in the header.
The implementation that uses the class lives in a .C file, not in the header.

Do not use forward declarations if you need to declare a member variable of that type directly in a struct or class, or if you need to use the full interface in the header itself. In that case, you must include the correct header.

Using Header Files with Interpreted Macros

ROOT can interpret both .C and .h files. For small analyses, you often run:

cpp
.x mymacro.C

Header files fit into this workflow naturally. You:

Write headers with declarations.
Write macros or .C files with implementations that include those headers.
Execute the entry macro with .x or let ROOT load compiled code.

For example, if you have:

Helpers.h:

cpp
#ifndef HELPERS_H
#define HELPERS_H
double InvariantMass(double e1, double px1, double py1, double pz1,
                     double e2, double px2, double py2, double pz2);
#endif

Helpers.C:

cpp
#include "Helpers.h"
#include <cmath>
double InvariantMass(double e1, double px1, double py1, double pz1,
                     double e2, double px2, double py2, double pz2) {
    double e  = e1 + e2;
    double px = px1 + px2;
    double py = py1 + py2;
    double pz = pz1 + pz2;
    return std::sqrt(e*e - px*px - py*py - pz*pz);
}

run.C:

cpp
#include "Helpers.h"
void run() {
    double m = InvariantMass(10, 1, 2, 3,
                             20, -1, -2, -3);
    std::cout << "Invariant mass: " << m << std::endl;
}

If you now run:

cpp
.x Helpers.C
.x run.C

ROOT reads the header in both cases, so the function signature is shared and consistent. In interactive workflows, it is common to .L (load) a file with implementations, then run a simple macro that calls those functions.

For example:

cpp
root [0] .L Helpers.C
root [1] .x run.C

The pattern is the same as for compiled programs, you just use ROOT’s interactive loader instead of building a binary.

Using Header Files with Compiled ROOT Code

When your analysis gets heavier, you will use ROOT’s compilation features. You may use ACLiC or build a standalone executable with a build system, but in both cases header files are necessary.

A common interactive pattern is:

cpp
root [0] .L Analysis.C+
root [1] runAnalysis()

The + tells ROOT to compile the code. When Analysis.C includes Analysis.h and both are in your project, ROOT compiles and links them with proper C++ rules. If you later modify only the implementation in Analysis.C, ROOT can often reuse compiled headers and only recompile what changed.

If you later move to a full build system, such as CMake, the same header files and source files can be used without change. That is one of the main reasons to adopt header files early in your ROOT projects.

Rule: If you plan to compile your ROOT code, any function or class that is used from outside its source file must have its declaration in a header file that is included where it is used.

Organizing Headers for Larger Analyses

As your analysis grows, it is helpful to split your headers by responsibility. For example:

Config.h
Contains constants, configuration structures, and simple helpers related to configuration and cuts.

HistDefs.h
Contains declarations of functions that create and configure histograms, or small structs that group histograms.

Analysis.h
Contains the main analysis function declarations, for example RunAnalysis, ProcessEvent, or higher level routines.

PhysicsUtils.h
Contains physics related helper functions, for example invariant mass calculations, kinematic transformations, or simple algorithms.

This modular organization lets you:

Include only what is needed in each source file.
Avoid long header chains that pull in unneeded dependencies.
Reuse modules across different analyses or projects.

You can also have a single general header for very small projects, then split it later without changing the rest of the code much, because all uses already include headers rather than .C files.

Common Pitfalls When Using Header Files in ROOT

There are a few typical problems beginners encounter when they start using header files with ROOT:

Forgetting include guards.
This leads to redefinition errors when a header is included multiple times. Always add guards as soon as you create the file.

Defining functions in headers unintentionally.
If you put a full function definition in a header and include it in more than one .C file, you may get multiple definition linker errors when compiling. For analysis helper functions this often appears as vague errors from ACLiC. Keep implementations in .C unless you use small inline functions intentionally.

Including macros instead of headers.
If you write #include "OtherMacro.C" from a macro, ROOT will jumble declarations and definitions together, which becomes hard to manage and compile. Use #include "OtherMacro.h" and keep OtherMacro.C only for implementations.

Using headers that depend on ROOT classes without including the ROOT headers.
If you use a class in a header and you do not provide a forward declaration or the necessary include, ROOT can interpret the macro but compilation will fail. Either include the correct ROOT header in your header file, or use a forward declaration when appropriate.

Changing headers without recompiling.
When using compiled code, if you change a header, you must trigger a recompilation. In ROOT with ACLiC, that usually means calling .L YourFile.C+ again. When using a build system, you must rebuild. If there is a mismatch between compiled code and headers, you may see confusing errors.

Being explicit about these rules and patterns makes your ROOT analysis code more robust and easier to debug.

Summary

Header files are a key element of writing better ROOT analysis code. By moving declarations of functions, classes, and configuration structures into headers, and keeping implementations in .C files, you:

Create a clear and reusable interface for your analysis.
Enable straightforward compilation with ROOT and with external build systems.
Reduce duplication and mismatches of function signatures.
Organize your code into logical modules that are easier to maintain.

If you start using header files early in your ROOT projects, you will have less work when your analysis scales up, and you will be much closer to standard, maintainable C++ practices.

Views: 11

Comments

Please login to add a comment.

Don't have an account? Register now!