KAHIBARO
Discord Login Register

25.7. G. ROOT C++ Cheat Sheet

General Syntax Reminders

C++ used inside ROOT follows standard C++ syntax, with a few conveniences provided by the ROOT interpreter. This section lists the most common elements you will use in ROOT analysis code.

Basic Program and Macro Structure

A minimal standalone C++ program:

cpp
#include <iostream>
using namespace std;
int main() {
   cout << "Hello ROOT C++" << endl;
   return 0;
}

A minimal ROOT macro function (in myMacro.C):

cpp
void myMacro() {
   cout << "Hello from ROOT macro" << endl;
}

Calling the macro from the ROOT prompt:

cpp
root[] .x myMacro.C          // interpreted
root[] .x myMacro.C(10, 2.5) // with arguments
root[] .L myMacro.C+         // compile with ACLiC
root[] myMacro();            // call compiled function

Use // for single line comments and / ... / for block comments.

Includes and Namespaces

Standard library includes:

cpp
#include <iostream>
#include <cmath>
#include <vector>
#include <string>
#include <algorithm>

ROOT includes (when compiling with a compiler):

cpp
#include "TFile.h"
#include "TH1F.h"
#include "TTree.h"
#include "TCanvas.h"
#include "TRandom3.h"
#include "TGraph.h"

At the ROOT prompt, most ROOT headers are already available. For standard C++ you still need includes when you compile with +.

Using namespaces:

cpp
using namespace std;

In ROOT, std:: prefix is often optional but you should use it in modern code:

cpp
std::cout << "Value = " << value << std::endl;

Variables and Data Types

Fundamental Types

Common C++ types for ROOT work:

cpp
int           nEvents   = 1000;
long          bigIndex  = 1000000L;
float         energy    = 3.14f;
double        mass      = 0.13957;
bool          passedCut = true;
char          letter    = 'A';

String types:

cpp
std::string   name = "pion";
// ROOT C-style string for some older interfaces
char          cname[16] = "histName";

Pointer syntax (very common with ROOT objects):

cpp
TH1F *h1 = new TH1F("h1", "Title", 100, 0, 10);
TFile *f = TFile::Open("file.root", "READ");

Type Conversions

Explicit casts:

cpp
double x = 3.7;
int    i = (int)x;       // truncates to 3
int    j = int(x);       // same

Automatic promotion in expressions:

cpp
int    n = 5;
double y = 2.0;
double z = n * y;        // n is promoted to double

Operators

Arithmetic Operators

cpp
a + b;  // addition
a - b;  // subtraction
a * b;  // multiplication
a / b;  // division
a % b;  // remainder (integers)

Increment and decrement:

cpp
i++;  // post-increment
++i;  // pre-increment
i--;  // post-decrement
--i;  // pre-decrement

Compound assignment:

cpp
x += 5;
y -= 2;
z *= 3;
w /= 4;

Comparison and Logical Operators

Comparison:

cpp
a == b;  // equal
a != b;  // not equal
a <  b;  // less than
a <= b;  // less or equal
a >  b;  // greater than
a >= b;  // greater or equal

Logical:

cpp
a && b;  // logical AND
a || b;  // logical OR
!a;      // logical NOT

Combined logical expressions, very common in cuts:

cpp
if (pt > 0.5 && fabs(eta) < 2.5 && charge != 0) { ... }

Control Flow

if, else if, else

cpp
if (x > 0) {
   cout << "Positive" << endl;
} else if (x < 0) {
   cout << "Negative" << endl;
} else {
   cout << "Zero" << endl;
}

Pay attention to braces when you have more than one statement.

for Loops

Standard index loop:

cpp
for (int i = 0; i < 10; ++i) {
   cout << "i = " << i << endl;
}

Loop over a std::vector by index:

cpp
std::vector<double> v = {1.0, 2.0, 3.0};
for (size_t i = 0; i < v.size(); ++i) {
   cout << v[i] << endl;
}

Range based loop (C++11 and later, works in modern ROOT):

cpp
for (auto value : v) {
   cout << value << endl;
}

while Loops

cpp
int i = 0;
while (i < 10) {
   cout << "i = " << i << endl;
   ++i;
}

switch

Useful for simple integer or enum cases:

cpp
int code = 2;
switch (code) {
   case 1:
      cout << "One"; 
      break;
   case 2:
      cout << "Two"; 
      break;
   default:
      cout << "Other"; 
      break;
}

Functions

Defining and Calling Functions

General pattern:

cpp
return_type functionName(arg_type1 arg1, arg_type2 arg2) {
   // body
   return value; // if not void
}

Example:

cpp
double kineticEnergy(double mass, double momentum) {
   return momentum * momentum / (2.0 * mass);
}

Calling:

cpp
double ke = kineticEnergy(0.5, 1.2);

Functions with no return value:

cpp
void printEvent(int i) {
   cout << "Event " << i << endl;
}

Function prototypes are needed before use in compiled code:

cpp
double myFunc(double x);   // declaration
double myFunc(double x) {  // definition
   return x * x;
}

ROOT interpreter is often more forgiving, but for compiled macros you must respect correct declaration order or use header files.

Passing by Value and by Reference

By value:

cpp
void incrementByValue(int x) {
   x += 1;        // original variable unchanged
}

By reference:

cpp
void incrementByRef(int &x) {
   x += 1;        // original variable is changed
}

Using references is common for output parameters:

cpp
void computeMeanRMS(const TH1 *h, double &mean, double &rms) {
   mean = h->GetMean();
   rms  = h->GetRMS();
}

Arrays and std::vector

C Arrays

Declaration:

cpp
int    a[5];            // uninitialized
double b[3] = {1,2,3};  // initialized

Index from 0:

cpp
a[0] = 10;
cout << b[2] << endl;

Arrays are often used with TGraph:

cpp
const int n = 3;
double x[n] = {1.0, 2.0, 3.0};
double y[n] = {2.0, 4.0, 6.0};
TGraph *gr = new TGraph(n, x, y);

std::vector

Declaration and initialization:

cpp
std::vector<double> v;             // empty
v.push_back(1.0);
v.push_back(2.0);
std::vector<int> ids = {11, 13, 211};

Access:

cpp
cout << v[0] << endl;
cout << v.at(1) << endl;  // bounds-checked
size_t n = v.size();

Iteration:

cpp
for (size_t i = 0; i < v.size(); ++i) {
   cout << v[i] << endl;
}
for (auto value : v) {
   cout << value << endl;
}

In TTrees, std::vector<T> is commonly used as a branch type for variable length data.

ROOT Specific C++ Idioms

Creating and Using ROOT Objects

Use new when you want objects to live on the heap:

cpp
TH1F *h = new TH1F("h", "My hist", 100, 0, 10);
h->Fill(1.0);
h->Draw();

You can also create objects on the stack in macros:

cpp
void myHist() {
   TH1F h("h", "My hist", 100, 0, 10);
   h.Fill(2.0);
   h.Draw();
}

Pay attention to ROOT ownership rules when working with files, canvases, and histograms attached to directories.

Accessing Members and Methods

Use . for objects, -> for pointers:

cpp
TH1F h("h", "title", 100, 0, 1);
TH1F *hp = &h;
h.Fill(0.5);      // object
hp->Fill(0.5);    // pointer

Input and Output

C++ stream output:

cpp
cout << "Mean = " << h.GetMean() << endl;

ROOT specific Print:

cpp
h.Print();

Formatted output via Form:

cpp
cout << Form("Entries = %d, Mean = %.3f", int(h.GetEntries()), h.GetMean()) << endl;

Pointers, References, and auto

Pointers

Declaration and usage:

cpp
TFile *f = TFile::Open("data.root", "READ");
if (!f || f->IsZombie()) {
   cout << "Could not open file" << endl;
   return;
}

Dereferencing:

cpp
TH1F *h = (TH1F*)f->Get("h1");
if (h) {
   cout << "Entries: " << h->GetEntries() << endl;
}

References

Binding a reference:

cpp
TH1F &href = *h;        // href is another name for *h
href.Fill(3.14);

References cannot be null. They are useful for function parameters.

auto Type Deduction

Handy with long ROOT type names:

cpp
auto c  = new TCanvas("c","c",800,600);
auto h1 = new TH1F("h1","h1",100,0,1);
auto tree = (TTree*)f->Get("events");

auto lets the compiler deduce the type from the initializer. Use it especially for iterator types.

Common ROOT Loop Patterns

Loop over Histogram Bins

cpp
int nbins = h->GetNbinsX();
for (int i = 1; i <= nbins; ++i) {
   double x    = h->GetBinCenter(i);
   double cont = h->GetBinContent(i);
   double err  = h->GetBinError(i);
}

Note that ROOT histogram bins start at 1 and go to nbins. Bin 0 is underflow and bin nbins+1 is overflow.

Loop over TTree Entries

cpp
TTree *t = (TTree*)f->Get("tree");
double energy;
t->SetBranchAddress("energy", &energy);
Long64_t nentries = t->GetEntries();
for (Long64_t i = 0; i < nentries; ++i) {
   t->GetEntry(i);
   if (energy > 10.0) {
      h->Fill(energy);
   }
}

Memory and new/delete Basics

ROOT often manages memory for you, but in modern C++ you still need to understand dynamic allocation.

cpp
TH1F *h = new TH1F("h","h",100,0,1);
// use h
delete h; // free memory when you created it with new and ROOT does not own it

If an object is written to a TFile or created on a TCanvas, ROOT may take ownership. In many analyses you rely on ROOT cleanup at end of session, but for long running programs you should manage lifetimes carefully.

Always match new with delete for objects you own. Do not delete objects that ROOT owns, such as histograms retrieved from a TFile directory, unless you explicitly detached or cloned them. Incorrect deletes can lead to crashes.

Preprocessor and Macros

Preprocessor Directives

Conditional compilation:

cpp
#ifdef DEBUG
   cout << "Debug info" << endl;
#endif

Include guards for headers:

cpp
#ifndef MYHEADER_H
#define MYHEADER_H
// declarations
#endif

Simple macro definitions:

cpp
#define N_BINS 100
#define PI 3.141592653589793

Use constants or constexpr in modern C++ instead of preprocessor where possible:

cpp
constexpr int N_BINS = 100;

Common Math Functions

Include <cmath>:

cpp
#include <cmath>
double y = std::sqrt(x);
double z = std::sin(phi);
double a = std::exp(-x);
double b = std::log(x);     // natural log
double c = std::log10(x);   // base 10 log
double d = std::fabs(x);    // absolute value

With ROOT you also have TMath:

cpp
#include "TMath.h"
double y = TMath::Sqrt(x);
double z = TMath::Sin(phi);

String Handling

std::string operations:

cpp
std::string name = "run";
name += "_42";
cout << name << endl;
if (name == "run_42") { ... }
const char *cname = name.c_str(); // for ROOT APIs that need const char*

Formatting with Form or std::to_string:

cpp
int run = 42;
std::string hname = "h_run_" + std::to_string(run);
TH1F *h = new TH1F(hname.c_str(), "title", 100, 0, 1);
TH1F *h2 = new TH1F(Form("h_run_%d", run), "title", 100, 0, 1);

Error Handling Patterns

Simple checks:

cpp
TFile *f = TFile::Open("input.root");
if (!f || f->IsZombie()) {
   cerr << "Error opening file" << endl;
   return;
}
TH1F *h = (TH1F*)f->Get("hData");
if (!h) {
   cerr << "Histogram hData not found" << endl;
   return;
}

Guard against division by zero:

cpp
double denom = h->Integral();
if (denom > 0) {
   h->Scale(1.0 / denom);
} else {
   cerr << "Cannot normalize, integral is zero" << endl;
}

Always check pointers returned by ROOT factory methods (like Get, Open, GetHistogram) before dereferencing them. Attempting to use a null pointer is a common source of segmentation faults.

Quick Reference Tables

Common fundamental types:

PurposeTypeTypical usage example
Integer countsintnumber of events, bins
Large integer indexlongentry index, sizes
Single precisionfloatdetector coordinates, quick vars
Double precisiondoubleenergies, masses, physics calc
True or falseboolcuts, flags
Charactercharsingle letter code
Text stringstd::stringfilenames, labels

Common loop patterns:

TaskPattern example
Simple counterfor (int i = 0; i < N; ++i) { ... }
Over vector elementsfor (auto v : vec) { ... }
TTree entriesfor (Long64_t i = 0; i < n; ++i) { t->GetEntry(i); }
Histogram binsfor (int i = 1; i <= nb; ++i) { ... }

These patterns and snippets cover most C++ constructs that you will routinely use with ROOT.

Views: 10

Comments

Please login to add a comment.

Don't have an account? Register now!