This paragraph is intended to introduce the
reader to some useful insights into C++, to allow some use of the most advanced
features found in ROOT. It is in no case a full course in C++.
IV.1 C++
notions used: classes, methods and constructors
You can try to use CINT to test the given
examples. Before that, exit the previous ROOT session, go to the directory
"$VEGA/vegatutorial", launch ROOT/VEGA and load into the environment the
tutorial macro vtutorial.C :
vega[]
.L vtutorial.C
The raw command .L loads a macro into the
interpreter environment. You can then use all the functions/classes/objects
defined inside it.
C++ extends C with the notion of class. If you’re used to structures
in C, a class is a struct, that is a group of variables that are related, which
is extended with functions and routines specific to this structure (class). What
is the interest? Consider a struct that is defined this way:
struct Line {
float x1;
float y1;
float x2;
float y2;
}
This structure is defined in vtutorial.C so
you don’t have to define it yourself. It represents a line to be drawn in
a graphical window. (x1,y1) are the coordinates of the first point, (x2,y2) the
coordinates of the second point.
In standard C, if you want to effectively draw such a line, you first have
to define a structure and initialize the points (you can try
this):
This defines a line going from the point
(0.2,0.2) to the point (0.8,0.9). To draw this line, you will have to write a
function, say LineDraw(Line
l) and call it with your object as
argument :
LineDraw(firstline);
In
C++, we would not do that. We would instead define a class like
this:
class TLine {
int x1;
int y1;
int x2;
int y2;
TLine(int x1, int y1, int x2, int
y2);
void Draw();
}
where we added two functions, that we will
call methods or member functions, to the TLine class. The first method is used
for initializing the line objects we would build. It is called a
constructor.
The second one is the Draw method itself.
So, to build and draw a line, we have to do:
TLine
l(0.2,0.2,0.8,0.9); l.Draw();
The
first line builds the object
l
by calling its constructor. The second line calls the Draw() method of this
object. You don’t need to pass any parameters to this method since it
applies to the object
l
and knows what are the coordinates of the line. These are internal variables x1,
y1, x2, y2 that were initialized by the constructor.
IV.2 C++
notions used: inheritance and data encapsulation
IV.2.1 Inheritance
There is an obvious question that could be
asked : what is the relation between a canvas and a pad ? Isn’t it almost
the same thing? Yes it is. In fact, a canvas is a pad that spans through
an entire window. This is nothing else than the notion of inheritance. The TPad
class is the parent of the TCanvas class.
So what? The most interesting thing is that
you can reuse the code written in the parent class. For example, cd() is a
method that TCanvas inherits from it’s parent, TPad. Let’s try
another example.
We’ve defined a TLine class that
contains everything necessary to draw a line. If we want to draw an arrow, is it
so different from drawing a line? We just have to draw a triangle at one end. It
would be very inefficient to define the class TArrow from scratch. Fortunately,
inheritance allows a class to be defined from an existing class. We would write
something like :
class TArrow : public TLine
{
int ArrowHeadSize;
void Draw();
void SetArrowSize(int
arrowsize);
}
The keyword "public" will be explained
later. The class TArrow now contains everything that the class TLine does, and a
couple of things more, the size of the arrowhead and a function that can change
it. The Draw method of TArrow will draw the head and call the draw method of
TLine. We just have to write the code for drawing the head!
IV.2.2 Method
overriding
Giving
the same name to a method (remember : method = member function of a class) in
the child class (TArrow) as in the parent (TLine) doesn't give any problem. This
is called overriding a method. Draw
in TArrow overrides Draw in TLine. There is no possible ambiguity since, when
one calls the Draw() method, this applies to an object which type is known.
Suppose we have an object
l
of type TLine and an object
a
of type TArrow. When you want to draw the line, you do :
l.Draw()
so Draw() from TLine is called. If you
do
a.Draw()
Draw() from TArrow is called and the arrow
a
is drawn.
IV.2.3 Public and
private : data encapsulation
We
have seen previously the keyword "public". This keyword means that every name
declared public is seen by the outside world. This is opposed to "private" which
means only the class where the name was declared private could see this name.
For example, suppose we declare in TArrow the variable
ArrowHeadSize
private as in :
private :
int
ArrowHeadSize;
.
Then, only the methods (=member functions) of TArrow will be able to access this
variable. Nobody else will see it. Even the classes that we could derive from
TArrow will not see it. On the other hand, if we declare the method Draw() as
public, everybody will be able to see it and use it. You see that the character
public or private doesn't depend of the type of argument. It can be a data
member, a member function, or even a class.
For example, in the case of TArrow, the base
class TLine is declared as public :
class TArrow : public TLine
{
This means that all methods
of TArrow will be able to access all methods of TLine, but this will be also
true for anybody in the outside world. Of course, this is true provided that
TLine accepts the outside world to see its methods/data members. If something is
declared private in TLine, nobody will see it, not even TArrow members, even if
TLine is declared as a public base class.
What if TLine is declared "private" instead
of "public"? Well, it will behave like any other name declared private in TArrow
: only the data members and methods of TArrow will be able to access TLine, it's
methods and data members, nobody else.
This may seem a little bit confusing and the
reader should read a good C++ book if he/she wants more details. Especially
since besides public and private, a member can be protected... But this is
another story.
Usually, one puts private the methods that
the class uses internally, like some utility classes, and that the programmer
doesn’t want to be seen in the outside world.
With "good" C++ practice (which we suppose
is used in ROOT...), all data members of a class are also set private. This is
called data encapsulation and is one of the strongest advantages of Object
Oriented Programming (OOP). By doing that, nobody can see the data members of a
class, except the class itself. So, from the outside world, if one wants to
access those data members, one should use so called "getters" and "setters"
methods, which are special methods used only to get or set the data members. The
advantage is that if the programmer wants to modify the inner workings of his
class, he can do so without changing what the user sees. The user doesn’t
even have to know that something has changed (for the better,
hopefully).
An example. In our TArrow class, we would
have set the data member
ArrowHeadSize
private. The setter method is
SetArrowSize(),
we don’t need a getter method :
class TArrow : public TLine
{
private :
int ArrowHeadSize;
public :
void Draw();
void SetArrowSize(int
arrowsize);
}
If one wants to define an arrow, he does
:
TArrow* myarrow = new
TArrow(1,5,89,124);
this will call the constructor of TArrow,
but also the constructor of TLine, which is the parent class of TArrow,
automatically. Then, we want to set the size of the head :
myarrow->SetArrowSize(10);
and draw it :
myarrow->Draw();
IV.3 C++
notions used: operators new and delete
Why is it so interesting to use new and
delete instead of malloc and free ? First, the operator new knows the size of
the object it has to initialize. You don’t need to play with it. Second,
and the most important, new calls the specified constructor of the class the
object belongs to. No need to do special initialization, you have to think about
it when you write your classes constructor.
But new also calls the constructor of all
the objects that your class contains. For example, the class TPad contains a
list of all objects in the pad. This list is itself an object of the class TList
and there is a call to the constructor of this list in the constructor of TPad.
We will see that new calls also automatically the constructor of the parent
class (see the inheritance paragraph).
When you begin to use new, don’t use
malloc anymore. Better said, try to avoid mixing new and malloc. There is a risk
to use free() instead of delete to release a memory that was allocated by new.
This is guaranteed to crash!