PandA-2024.02
|
In this document we plan to shortly explain some useful guidelines to help you writing "good" code into the PandA Project. The main features which will be explained are:
This section explains the coding style used through out the PandA project; you are strongly advised to follow it.
for(int i = 0; i < n; i++) { ........... }
/** * ....your documentation..... */while the comments of class attributes are indicated as:
///......your documentation....
An important aspect, not only connected with the programming style, but also with the efficiency of the produced code regards the use of the const
keyword; this keyword means that the object it refers to can't be modified. In presence of this keyword, the C++ compiler performs optimizations which speed-up the execution; in particular use it after a method declaration:
void print() const;
This means that "this" (i.e. the internal representation of the class which contains the method) isn't modified by the method call.
Finally something not directly connected with the programing style: when, in a class or in a struct you call methods belonging to the struct itself or access attributes of that structure (or class), use always the "this->" elements. This is absolutely not necessary in order to have a working code, but it helps understanding your code: it is, for example, immediately clear what are the local variables of the method you are in and what are the global attributes of the structure (or class). Example:
class MyClass { int attr1; void method1() { std::cout << this->attr1 << std::endl; } };
Note the use of "this->attr1" and not simply "attr1".
We would like to use pure C++ instructions and coding style, hence functions such as printf
, sprintf
or atoi
shouldn't be used. Most of the replacements for these functions can be found in the STL standard C++ library (http://www.sgi.com/tech/stl/) or in the BOOST library (http://www.boost.org/); for example: -printf
can be replaced by the <<
operator: in order to write something to the standard output you do:
int a = 5; std::cout << "Writing to stdout the number " << a << std::endl;
int a = 5; std::string newString(boost::lexical_cast<std::string>(a));
Using exceptions and assertion to check errors is a good programming habit; you are strongly advised to used exceptions whenever you feel in order to signal that a method terminated because of an error or because of a particular condition. You may define your own exceptions and most of the time you should do this. Anyway PandA provides two mechanisms you may use to signal that en error occurred and that the program must terminate:
THROW_ERROR(msg)
is a macro included in src/utility/exceptions.hpp; when used, it terminates the program writing the "msg" string to the screen.THROW_ASSERT(cond, msg)
is a macro included in src/utility/exceptions.hpp; it is used to check that the boolean condition "cond" is true, otherwise the program termiantes and "msg" is written to the screen.Yuo may also write your own exception types, but first of all give a look in src/exceptions: this directory contains the definition of many common exceptions which are used throughout PandA, so you may find what you need here. Moreover, when defining you own exceptions, you may used the ones in src/exceptions as example.
In order to speed-up the compilation and in order to avoid some weird errors that sometimes happen, you should try to put all the #include
directives in the .cpp file and not in the .hpp. Instead of the inclusion in the hpp file you can use forward declarations. An example to make the concept clearer; lets suppose I have file1.hpp and file1.cpp which need the declaration of the class contained in file2.hpp:
file2.hpp: class class2 { /** * A method.... */ void doSomething(); }; file1.hpp: //Instead of including file2.hpp I make a //forward declaration of class2 class class2; /** * This is class1 */ class class1 { //Attribute of type Class2 class2 &attibuteClass2; /** * Constructor of class1 */ class1(class2 &_attr); /** * A method.... */ void oneMethod(); }; file1.cpp: #include "file1.hpp" #include "file2.hpp" /** * Constructor of class1 */ class1::class1(class2 &_attr) : attibuteClass2(_attr){} void class1::oneMethod() { this->attibuteClass2.doSomething(); }
Of course you can do that unless the implementation of part (or of all) the functionality is in your .hpp file:
file2.hpp: class class2 { /** * A method.... */ void doSomething(); }; file1.hpp: //I have to include file2.hpp, a forward declaration is not enough #include "file2.hpp" /** * This is class1 */ class class1 { //Attribute of type Class2 class2 &attibuteClass2; /** * Constructor of class1 */ class1(class2 &_attr); /** * A method.... */ void oneMethod() { this->attibuteClass2.doSomething(); } }; file1.cpp: #include "file1.hpp" #include "file2.hpp" /** * Constructor of class1 */ class1::class1(class2 &_attr) : attibuteClass2(_attr){}
In this situation we are forced to include "file2.hpp" in "file1.hpp" file: this because in "file1.hpp" we don't simply use the declaration of class2 but also the details about its internal implementation (i.e. the knowledge of the method doSomething
).
Regarding header files, you know that at the beginning of the file you have to used an #ifndef
clause (this is done in order to avoid multiple compilations of the definitions contained in the file). The convention used is that the argument of the #ifndef
must be the upper case version of the name of the file itself. For example for file "nameExample.hpp":
#ifndef NAMEEXAMPLE_HPP #define NAMEEXAMPLE_HPP //your code here ....... #endif
It is common knowledge that it is possible to easily create pointers to C++ objects (using the new
keyword), and that these pointers can be passed around in your program. The main limitation of this approach regards the fact that the developer must remember to deallocate these dynamically created objects, no garbage collection is performed (as opposed to what happens with the Java language). PandA uses a special a trick to implements a kind of garbage collection: the refcount; if you are using the boost library with a version greater than 1.33 then the refcount mechanism inside boost is automatically enabled, otherwise you will be using the library contained in src/utility/refcount.hpp (actually all the developers must include in their files src/utility/refcount.hpp: then the file itself decides whether it is appropriate to use the boost library or not).
This is a simple example on the utilization of refcounts:
#include "refcount.hpp" int main() { //Creation of a dynamic object class1 *dynObject = new class1(); //Wrapping of the object into a refcount refcount<class1> dynObjectRef(dynObject); //Utilization of the refcount dynObjectRef->oneMethod(); }
Note that in case you use refcounts, the forward declaration (technique seen in the previous paragraph) changes a bit: using "class className;" for the forward declaration may not be enough, it your code uses refcounts you need to substitute "class className;" with "REF_FORWARD_DECL(className);".
Wherever it is possible you should use refcounts instead of pointers to objects.
It is important to often insert in your code debugging statements, which prints useful information about the state of your program; all the helper mechanisms which can be used for this purpose are contained in file "dbgPrintHelper.hpp". So far five verbosity levels are defined, the lowest the less verbose, the highest the most. You may used the PRINT_DBG_MEX
macro to print your messages. Note that it may happen that the debugging statements you want to used are something more than a mere message: you may then insert your own debug instructions in the code, but make sure they are inserted into a "#ifndef NDEBUG" statement: this way, it the variable NDEBUG is defines (so if we are producing a release of the code), no debug statement is inserted and the code can run faster. For example
#ifndef NDEBUG if(this->debugLevel >= DEBUG_LEVEL_PEDANTIC) { for(int i = 0; i < this->numFunctions; i++) this->taskGraph[i]->writeDot((std::string("TaskGraph") + boost::lexical_cast<std::string>(i)).c_str()); } #endif