Let’s first do a simple “Hello world” example and try to run it in ROOT environment, as a stand-alone c++ program, and also using the Python-ROOT interface called PyROOT. Because c++ is inherently an object oriented language, let’s make the “hello world” program using objects.
First, we create a header file “HelloWorld.h”. It contains declaration of our “HelloWorld” class:
#ifndef HELLOWORLD_H #define HELLOWORLD_H class HelloWorld { public: /** * @brief Construct a new Hello World object */ HelloWorld(); /** * @brief The main execute method of the Hello World example */ void execute(); }; /** * @brief Executable function */ void runMe(); #endif
Let’s look at the code more closely:
- Note that the code is protected using #ifndef … #endif clause. This is important when working on bigger projects where the same header file is included in more places.
- The class HelloWorld has two member functions (so called methods). The first one has no type (is declared without the type name) and has the same name as the class itself. This is so called “constructor”. This method is called when the instance of the class “HelloWorld” is created in memory and typically contains initialisation of all the member attributes etc. The second method “execute” has type void (meaning that it will not return any value) and it will contain the bulk of our program.
- Each of the methods is adorned by comments briefly describing what they do. It’s not compulsory, but it’s a good practice to comment your code. Good IDE (like Visual Studio Code) will create pre-formatted comment skeletons for you.
- Finally, we also added a simple function “runMe”. Note that this is declared outside the class! We will use it to execute the program.
- The header file only contains declaration of the methods, but not the actual implementation of the methods’ body (so called definition). That will be stored in a separate file.
Now let’s implement the bodies of the methods. We will need a second file called “HelloWorld.cpp”:
#include "HelloWorld.h" #include <iostream> HelloWorld::HelloWorld() { // nothing to do here. The class has no member data so // nothing to be initialized } void HelloWorld::execute() { // print the message std::cout << "Hello world" << std::endl; } void runMe() { HelloWorld* hw = new HelloWorld(); hw->execute(); }
- In this source file, the two methods of the “HelloWorld” are implemented. The names, types, and parameters of the methods must be the same as in the header file. However, we need to tell the compiler that the methods’ implementations belong to the HelloWorld class by attaching the “HelloWorld::” prefix to each of the name.
- The constructor is empty, because we do not have any data members that could be initialised in this class. In the second method we print out simple “Hello world” message onto the screen. It is done using the c++ standard library class “cout“.
- In the first line we include the “HelloWorld.h” header file so that it becomes part of the source file. If we’d only wanted to make a super-simple project, we could have just put the class declaration inside the same file as the methods’ bodies. However, it is a good practice to put the declarations in separate files, as we will see later.
- In the second line we include header file with declaration of the cout class. This is needed so that when we compile the program the compiler knows how to interpret “std::cout” commands.
- Some of the standard c++ commands are declared inside the std namespace. It just means that when we call them we have to attach “std::” prefix to the class/function name. We will see a lot of these in the next exercises. The namespaces are useful to make code more organised.
- In the “runMe” function we create an instance of the HelloWorld class and execute its one method. It is done using the “new” operator. The instance is created in the memory and the pointer to it is returned and stored in variable “hw”. In the second line we call the execute method and the “Hello world” message is printed on the screen.
Running the example using ROOT
The ROOT framework is build around C++ interpreter. When we run the root passing our simple program as a parameter, the ROOT will read it line-by-line and interpret each command. This is the simplest way to run programs (interpreted programs are called macros), but it is by far slowest and also the interpreter has its limitations (i.e. it will not be able to execute all the c++ constructs). Despite the limitations, sometimes it can be useful to run this way if you only need something super simple. Give it a try:
> root HelloWorld.cpp
Note: I use symbol “>” to mark points where the commands are entered into the command line prompt by the user. This is to distinguish entered commands from program printouts. The symbol itself is not part of the command!
You will see an output like this (provided you have the same version of the ROOT )
------------------------------------------------------------ | Welcome to ROOT 6.16/00 https://root.cern | | (c) 1995-2018, The ROOT Team | | Built for linuxx8664gcc on Feb 03 2019, 09:52:00 | | From tag , 23 January 2019 | | Try '.help', '.demo', '.license', '.credits', '.quit'/'.q' | ------------------------------------------------------------ root [0] Processing HelloWorld.cpp... (HelloWorld) @0x56254090bc30 root [1]
You see that ROOT has processed your program and now it waits for your command. Now we can use the command line to run the “runMe” function:
> runMe() Hello world > .q
Compiling the macro using ROOT
While interpreting the c++ commands is fine for small macros, it is unusable for larger projects since it becomes too slow. However, there is a simple way to compile code within the ROOT framework. If you add “+” sign at the end of the macro name when running root, it will get compiled before execution:
> root HelloWorld.cpp+ root [0] Processing HelloWorld.cpp+... Info in <TUnixSystem::ACLiC>: creating shared library /home/scheirich/workdir/AnalysisTutorial/01/./HelloWorld_cpp.so > runMe() Hello world
You see that there is an extra printout telling you that the code was compiled and the shared library was created. The compiled code runs much faster than the interpreted one so in most cases I recommend to use the compiled option.
Compiling the code using makefile
While for small projects it is OK to use the ROOT-integrated compiler interface, when you work on larger projects it is much better to compile the code yourself using the c++ compiler directly. The reason is that ROOT will only compile your code if it is all in one cpp file. This means that huge project with many classes will always be compiled in its entirety whenever you change a small bit.
There is an alternative way which is also more standard in c++ programming. We will need to create two additional files:
- the header file called “Linkdef.h”
- the so called “Makefile”
Let’s start with the “Linkdef.h”. This is file that contains list of classes that should be made visible to ROOT. It follows the following syntax:
#ifdef __CINT__ #pragma link C++ nestedclass; #pragma link C++ nestedtypedef; // includes all header files #include "HelloWorld.h" // All classes #pragma link C++ class HelloWorld+; // all functions #pragma link C++ function runMe; #endif
Whenever you add a new class into your project, you just need to add in into the “Linkdef.h” header file.
Now let’s write the Makefile. Makefile is a file interpreted by a standard tool called “make”. It contains set of rules for how the program should be compiled. Makefiles can be quite complicated, but once you have a working Makefile you can re-use it over and over again. So this is how the Makefile should look like for our “Hello world” project:
You can re-use this file for all your projects, only changing the first part of the file:
#----------------------------------------------------------------------------- PROGRAM = HelloWorld HDRS = HelloWorld.h\ OBJS = HelloWorld.o\ dict_$(PROGRAM).o #-----------------------------------------------------------------------------
Here you have to specify the name of your program (this is used to name the .so shared library. Here we chose to name it with the same name as the main class, but it can have completely different name). In the second variable list all your header files. if you have more than one, put them on separate lines, but note that there has to be a backslash “\” at the end of the line. The final variable lists the compiled objects. Basically, there should be one “.o” file for every “cpp” file.
Note that there is also a “dict_$(PROGRAM).o” file added into the list. This is so called ROOT dictionary. The ROOT dictionary is a code generated by the ROOT framework that will allow it to see the classes listed in the “Linkdef.h” from within the ROOT command line. It is also needed if we want to import out code into Python (see next section).
Now you can compile your Hello world project simply by typing:
make clean make
You can now execute the program from ROOT, but this time you give it the name of the “.so” library instead of the “.cpp” file:
> root HelloWorld.so root [0] Processing HelloWorld.so... > runMe() Hello world
You may ask why do things this way when it is much more complicated than compiling your code with ROOT. The reason is that if you start to have more than one class in your project (which is often the case), make will compile them one at a time (creating .o file for all .cpp files) and then links them together. When you modify one file, make will only recompile the modified piece of code and not everything. Also, your code will be much better organised if you can put classes into separate files.
Using Python
ROOT has interface into scripting language Python. While Python is slower than compiled c++, it is much easier to work with. Esp. dealing with strings, arrays, files, etc is much easier in python. Therefore, the optimal way to work is to write pieces of the code that require speed in c++ (using the procedure outlined above) and everything else (plotting, book keeping, job handling etc) in Python.
If your ROOT is installed correctly, you can call the HelloWorld class from inside Python like this. Create a script “runMe.py”:
# here we load the shared library created using the Makefile from ROOT import gSystem gSystem.Load("HelloWorld.so") # now we can create instance of the class in python using its syntax from ROOT import HelloWorld hw = HelloWorld() hw.execute() # we can also call the function runMe from ROOT import runMe runMe()
Note that in Python there are no pointers. Instances are created without the “new” keyword and access operator is “.” instead of “->”. Otherwise, the classes / functions we have created in c++ will have the same name and parameters in python.
To execute the macro just type:
> python runMe.py Hello world Hello world
The message is printed twice because we call execute directly and second time via the “runMe” function.