eLynx SDK
v3.3.0 C++ image processing API reference |
K.I.S.S.* as much as possible when writing code.
Rules are the best way to share methods and go further in project development. I fix myself rules (I try) to write clear code. The goal is to kill bad code writing side effect. This way you have more time developping than debugging. Since it doen't feel fun at first sight, it is when you have understand why you must use rules. As the project grow bigger one reach quickly at being overloaded by maintenance because of quality lack. Keep in mind that once you known and apply these rules, you just focus on development not on how the write it in C++. To have quality, just need good rules.
Have fun and happy coding.
* Keep It Simple Stupid.
For sharing reason of sources, all source files are to be written in English.
eLynx sdk project is designed for multi-platform and multi-programmer environment. For readability reason of the source code over many text editors, it would be safe yo have the same text formatting.
<------------------------------ 79 characters ------------------------------->
//----------------------------------------------------------------------------
Use the following primary types :
Bits | unsigned integer | signed integer | floating point |
8 | eLynx::uint8 | eLynx::int8 | none |
16 | eLynx::uint16 | eLynx::int16 | none |
32 | eLynx::uint32 | eLynx::int32 | float |
64 | eLynx::uint64 | eLynx::int64 | double |
You must include definition file CoreTypes.h to access these types.
All the code MUST be written in English.
TODO
TODO
Methods of functions names MUST begin by a verb (action), and be writen in mixed case starting with upper case. Execption for event handler that begins with 'On'
Purpose |
Naming |
Example |
Data access | SetData(...) et GetData(...) | see accessor |
returning aggregation creation | CreateObject(...) | Cube * CreateCube(...) |
destroy agregated object created with CreateObject | Remove(...) | Remove(Cube *) |
building agregated object | BuildObject(...) | BuildCube(...) |
Action on an objet | 'Action'Object(...) | MoveTriangle(...) |
Event handler | OnEvent(...) | OnComboChange(...) |
return of boolean state | IsState(...) | IsSelected(...) |
return of boolean attribut | HasAttribute(...) | HasTexture(...) |
return pre-conditional state for an action | CanAction(...) | CanSave(...) |
todo | Compute | todo |
todo | Update | todo |
Not to make of amalgam between GetObject which recovers a reference, CreateObject which recovers an aggregation from external method and BuildObject which recovers an aggregation from internal method. A frequent error is to turn over an aggregation with the Get method. That can generate an memory leak. However, there is an exception to this principle, if Get creates the object in a garbage collector.
todo
Macro are written in uppercase using underscore to separate word and prefixing by elx.
elxASSERT(); elxSAFE_DELETE(pnObject);
TODO
TODO
A variable name is composed by tree informations : [Visibility]{Type}[Meaning]
Visibility is a required information that grealty help avoiding maning side effects. This point allows to save long debugging time for stupid errors.
Prefix |
Meaning |
(nothing) | method or function local variable |
_ (underscore) | class or struct data member |
_s (static) | static class or struct data member |
i (input) | input only method/function parameter |
o (output) | output only method/function parameter |
io (input and output) | input and output method/function parameter |
g_ (global) | global variable (banned) |
the_ (the only one) | singleton object (specific global) |
s_ (static) | module static variable |
ms_ (method's static) | method or function static variable |
In resume :
Prefix |
Meaning |
_? | class/struct data member (normal _, or static _s) |
?_ | data out of a class/struct (global g_, local s_, singleton the_, method/function static ms_) |
i,o,io | method or function parameter |
(nothing) | method or function local variable |
This information is required for the pointers type in order to control the pointed object life-cycle. It is optional for the other types of data. I recommand to use it for boolean and counter variables. You can define your own types convention but don't use type name beginning with i,o or s to respect the visibility rules.
Boolean : b, Ex: bEnd, bSuccess
Counter : n (number of) Ex: nPoint, nItem
The meaning is in mixed case starting with upper case.
Exception : local variable i for index or counter (not an input),
Ex: in file MyClass.h
extern uint g_Value; // global variable BANNED class MyClass { public: MyClass(); void SetValue(uint iValue); uint GetValue() const; void DoSomething(); protected: uint _Value; static uint _snRef; // class static as class instance reference counter };
in file MyClass.cpp
#include "MyClass.h" MyClass the_ClassInstance; // singleton uint g_Value = 1; // global variable BANNED static uint s_nCall = 0; // module variable uint MyClass::_snRef = 0; // class static variable MyClass::MyClass() : _Value(0) { _snRef++; // class static } // default constructor void MyClass::SetValue(uint iValue) // input parameter { _Value = iValue; // class member equals input parameter } // SetValue uint MyClass::GetValue() const { return _Value; // class member } // GetValue void MyClass::DoSomething() { static bool ms_bFirst = true; // method static variable if (ms_bFirst) { ms_bFirst = false; ... } s_nCall++; // module static } // DoSomething
The use of the pointers of the c++ causes great confusion. The c++ not being enough precise to completely describe the implication of a pointer, the only way of avoiding these confusions is to set nomination rules. The limitations of the c++ rise from the lack of information on the one hand on the structure and on the other hand on the cycle of life of the data carried by a pointer.
In order to manage well the pointed object life cycle it is fundamental to name them with precision. There are two great types of pointer:
Smart pointer are not taken into account ...
A pointer is said by reference, when its user (customer) manages neither construction, nor destruction of the pointed object. Referencing is a relative characteristic seen of an object or a method, i.e. that a aggregated pointer incorporated by an object, is seen referenced by another object or another method. A referenced pointer is prefixed by pr as pointer on reference.
int * CreateList() { int * plArray = new int [10]; // the owner is the method int * <b>pr</b>Item = plArray; // just a reference for (int i=0; i<10; i++) *<b>pr</b>Item++ = i; return plArray; // method transfert aggregation to the client } // CreateList
A pointer is said by aggregation, when its user (client) deals either on construction, or on the destruction of the pointed object. The dispersion of the responsibilities for the cycle of life of an object is harmful. Most of the time it is the proof of a bad design. Dispersion i.e. when an object is in responsibility of build and that another is in responsibility of destroy, very often generates memory leaks or crash.
As much as possible respect this rule: that which creates is that which removes.
TODO common bad design sample
pointer prefix |
construction |
destruction |
pn (new/delete) | pnObject = new Object; | delete pnObject; pnObject = NULL; |
pl (liste new[]/delete[]) | plObject = new Object [n]; | delete [] plObject; plObject = NULL; |
pm (malloc/free) | pmObject = (Object*)::malloc(sizeof(Object)); | if (NULL != pmObject){::free(pmObject); pmObject = NULL;} |
pf (object factory method) | pfObject = Factory.Create(..) | Factory.Remove(pf Object); |
ps (object self destroy) | psObject = ? creation method | if (NULL != psObject) { psObject->Release(); ps Object= NULL;} |
pa (unlistedaggregation) | paObject = ? creation method | unlist destruction method; paObject = NULL; |
pg (garbage collector) | pg Object = ? creation method | pgObject = NULL; at end of program call to a garbage |
Garbage collector pg: one creates an object with a factory and this factory deals with the destruction of this object.
None defined aggregation pa: when one is with an aggregation not in cases pn, pl, pm, ps, pf and pg. You known that you have allocate a ressource and that you have to de-allocate it with the corresponding function. For example :
// construction FILE * paFile = ::fopen(...); ... // destruction ::fclose(paFile); paFile = NULL;
This type of pointer is to be proscribed, but as in very good software, it forms part of a heavy heritage of approximate design. It is used to define pointers used with multiple modes of construction/destruction. One associates a variable then to him defining the good manners to destroy the object. A mixed pointer is prefixed by pM (Mixtes, Multiple). Example :
TODO
TODO
The header filename is the same as the class name. It must have the .h extension. Only declare one class per file.
Ex : file MyClass.h define the class MyClass.
//---------------------------------------------------------------------------- // MyClass.h [lib].Component package //---------------------------------------------------------------------------- // Usage : [Description] //---------------------------------------------------------------------------- // Inheritance : // MyClass // +BaseClass //---------------------------------------------------------------------------- // Copyright (C) [this year] by eLynx project // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Library General Public License for more details. //----------------------------------------------------------------------------
Draw the inheritence of class as it is a object class [Inheritance].
// Inheritance : // MyClass // +BaseClass
means that MyClass derive from BaseClass.
#ifndef __MyClass_h__ #define __MyClass_h__ #include "[lib]Lib.h" #include "BaseClass.h" ... namespace eLynx { class ExportedBy[lib] MyClass : public BaseClass { ... }; } // namespace eLynx #include "inl/MyClass.inl" #endif // __MyClass_h__
The inline filename is the same as the class name. It must have the .inl extension. Only declare one file per class.
Ex : file MyClass.inl implement inline methods of the class MyClass.
//---------------------------------------------------------------------------- // MyClass.inl [lib].Component package //---------------------------------------------------------------------------- // Copyright (C) [this year] by eLynx project // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Library General Public License for more details. //----------------------------------------------------------------------------
// Include statement #include "SomeClass.h" ... namespace eLynx { //---------------------------------------------------------------------------- inline returntype MyClass::Method1(...) { ... } // MyClass::Method1 //---------------------------------------------------------------------------- inline returntype MyClass::Method2(...) { ... } // MyClass::Method2 ... } // namespace eLynx
The implementation filename is the same as the class name. It must have the .cpp extension.
Ex : file MyClass.cpp implement the class MyClass.
//---------------------------------------------------------------------------- // MyClass.cpp [lib].Component package //---------------------------------------------------------------------------- // Usage : Class to represent my class //---------------------------------------------------------------------------- // Copyright (C) [this year] by eLynx project // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Library General Public License for more details. //----------------------------------------------------------------------------
#include "MyClass.h" ... namespace eLynx { ... } // namespace eLynx
//============================================================================ // [MyLib]Lib.h [MyLib].Component package //============================================================================ // Usage : declare [MyLib].Component access from dll. //---------------------------------------------------------------------------- // Copyright (C) [this year] by eLynx project // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Library General Public License for more details. //---------------------------------------------------------------------------- #ifndef __[MyLib]Lib_h__ #define __[MyLib]Lib_h__ // for windows platform #ifdef elxWINDOWS # ifdef ELYNX_[MYLIB]_EXPORTS # define ExportedBy[MyLib] __declspec(dllexport) // declaration in dll library. # else # define ExportedBy[MyLib] __declspec(dllimport) // client of the dll library. # endif #else # define ExportedBy[MyLib] // client of the static library. #endif #endif // __[MyLib]Lib_h__
//============================================================================ // [MyLib]Package.h [MyLib].Component package //============================================================================ // Usage : package of [MyLib].Component definition //---------------------------------------------------------------------------- // Copyright (C) [this year] by eLynx project // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Library General Public License for more details. //---------------------------------------------------------------------------- #ifndef __[MyLib]Package_h__ #define __[MyLib]Package_h__ #include "[MyLib]Types.h" #include "[MyLib]Errors.h" #include "[MyLib]Macros.h" #include "Something.h" ... #endif // __[MyLib]Package_h__
It is important to document the methods in the .h well and to take again this information to deepen them in the cpp. That can appear tiresome with the first access, but, the fact of duplicating information, makes it possible to better become aware of the relevance of the prototyping of the method. This stage is of primary importance for a new development. Indeed, it will emphasize all the weaknesses of precipitation. One forces oneself to think twice. The first time at the time of prototyping in the .h, the second at the time of the implementation in the .cpp. That makes it possible to check the control of the following points:
//---------------------------------------------------------------------------- // [Method name] : [Description] //---------------------------------------------------------------------------- // [Visibility] //============================================================================ // [Object life-cycle management] //============================================================================ // In : {Stack parameter list} // ... // Out : {Return} //---------------------------------------------------------------------------- returntype MyClass::[Method name]({Stack parameter list}) {Constness} { ... [Object life-cycle management] ... return returntype; {Return}
} // [Method name]
Here is a methodology to fill all the differents fields of the method header. Follow the ordered check list of questions to ask yourself to check the quality of both prototype and implementation.
This is the name of the method to implement.
Q1 : is this name simple ? clear ? accurate ?
Here is functionnality description of the method. Add the limitations, the algorithm description ... all that a caller must be informed of. You don't need to fill the description if it is redundant with its name or implicit.
//---------------------------------------------------------------------------- // MyMethod Q1 Q2 : allows to ... //---------------------------------------------------------------------------- returntype MyClass::MyMethod Q1 Q2 (...) { } // MyMethod Q1 Q2
//---------------------------------------------------------------------------- // MyMethod : allows to ... //---------------------------------------------------------------------------- returntype MyClass::MyMethod (...) const Q3 { ... } // MyMethod //---------------------------------------------------------------------------- // MyMethod2 : allows to ... //---------------------------------------------------------------------------- returntype MyClass::MyMethod2 (...) // not const Q3 { ... } // MyMethod2
One indicates the visibility of the method by picking again information of the header.
For a class method ask yourself :
For a module function ask yourself :
If a class method is virtual, to indicate the name of the class from where virtuality appears. So check, in the file .h, that the declaration of the method has the keyword virtual and a comment // virtual from ... . If this information is not present, add it. This helps understanding, the role of the class in the software architecture.
Example in MyClass.h
class MyClass : public OtherClass { ... public: // virtual from BaseClass Q5 virtual returntype MyMethod (...); Q4 Q6 ... protected: // virtual from OtherClass Q5 virtual returntype OtherMethod (...); Q4 Q6 ... };
In MyClass.cpp
//---------------------------------------------------------------------------- // MyMethod: allows to ... //---------------------------------------------------------------------------- // public virtual from BaseClass Q4 Q5 Q6 Q7 //---------------------------------------------------------------------------- returntype MyClass::MyMethod (...) { ... } // MyMethod //---------------------------------------------------------------------------- // OtherMethod : allows to ... //---------------------------------------------------------------------------- // protected virtual from OtherClass Q4 Q5 Q6 Q7 //---------------------------------------------------------------------------- returntype MyClass::OtherMethod (...) { ... } // OtherMethod
One will frame this information with two lines of characters '='. That allows to a client of the method to clearly and quickly identify his responsibility with respect to the life-cycle of the managed objects used by the method. This is the contract between client and method in 'black-box' mode. That is the method witch fix the contract and the client that accepts it. When this kind of information is missing every method's client coder must looks at the method code to understand there responsability. In most cases they don't care about and gererated leaks without knowing (but "it's working"). Soon or later memory problems come to remember them they are bad coder. The coder to blame is the one who don't expose the contract. This rules is for saving your client wasting time !
//---------------------------------------------------------------------------- // CreateMyObject : allows to ... //---------------------------------------------------------------------------- // public virtual from BaseClass //============================================================================ // caller is responsible to delete the return pointer when no longer used Q8 //============================================================================ MyObject * MyClass::CreateMyObject() { MyObject * pnMyObject = new MyObject(); return pnMyObject; Q8 } // CreateMyObject
If method has no parameter, use the MyMethod() notation. Don't use MyMethod(void).
For each parameter in the list ask yourself :
Describe here that kind of value the method returns. To specify the various possible values of return and their significances. If it returns a pointer on aggregation, to check [Object life-cycle management].
Ex: a full method header :
//---------------------------------------------------------------------------- // CanAttach : checks if we can attach a other MyClass to this //---------------------------------------------------------------------------- // public //---------------------------------------------------------------------------- // In : const MyClass& iOther : other object to check attachment with // Out : bool : true if iOther can be attached, false otherwise //---------------------------------------------------------------------------- bool MyClass::CanAttach(const MyClass& iOther) const { ... } // CanAttach
One uses different patterns according to the variable type. If the class variable is static, methods accessor are also static. Take care of removing method constness in the following patterns.
uint _Value; void SetValue(uint iValue) { _Value = iValue; } uint GetValue() const { return _Value; } void GetValue(uint& oValue) const { oValue = _Value; }
TODO
TODO
MyObject _Object; void SetObject(const MyObject& iObject) { _Object = iObject; } // retrieves an object reference MyObject& GetObject() { return _Object; } const MyObject& GetObject() const { return _Object; } // retrieves an object copy (call the object copy constructor) void GetObject(MyObject& oObject) const { oObject = _Object; } MyObject GetObject() const { return _Object; } // FORBIDDEN always return object reference
For performance reasons, always implement Get returning the object reference (&). |