IV More
on C++
(for the interested reader)
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):
- Line firstline;
firstline.x1 =
0.2;
firstline.y1 = 0.2;
firstline.x2 =
0.8;
firstline.y2 = 0.9;
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!
Damir BUSKULIC
Last update :19/11/2001;