It is a very good thing to make a modular architecture. It permits creating several implementation for one functionnality (several ways to do it), and to easily switch from one to the other, or change a part of the program.
The most obvious way to do that is inheritance. You create an interface, which defines what your implementation must be able to do, and you inheritate several implementations that do it with different ways. Then anywhere else you always use the interface class, and it will use the implementation you initialized with, no matter what it is.
Let's take the example of a class that must tell if a pixel belongs or not to a category of colors :
class ColorClass { public: virtual bool belongs(Pixel pix) = 0; }; class ColorClassRGB : public ColorClass { int minLum; double minCos; public: ColorClassRGB(int minLum, double minCos); virtual bool belongs(Pixel pix); }; class ColorClassHSV : public ColorClass { int minH, maxH; public: ColorClassHSV(int minH, int maxH); virtual bool belongs(Pixel pix); }; class Vision { public: void init(); void use(Image &img, ColorClass *color); }; Vision vision; void Vision::init() { // ColorClass *color = new ColorClassRGB(20, 0.99); // one ColorClass *color = new ColorClassHSV(20, 50); // or the other use(img, color); } void Vison::use(Image &img, ColorClass *color) { for(int i = 0; i < img.w; i++) for(int j = 0; j < img.h; j++) if (color->belongs(img[i][j])) img[i][j]=Pixel(0,0,0); }
It does the job, but it is not the best way, because all functions must be virtual, so they cannot be inlined and the code will be very slow if functions are called often.
Moreover this does not really respect the philosophy of inheritance. Inheritance represents the relationship “is a kind of”. But ColorClassRGB
is not a kind of ColorClass
, it is a ColorClass
implementation.
Eventually, it does not always work. If you create the Animal
class with a virtual clone
function (that returns an Animal
of course), and a Cow
class which inherits from Animal
, you would like that the clone
function of Cow
return a Cow
, but you can't (overriden functions must have same signature). Thus your clone
function must return an Animal
, and if you want to use it as a Cow
(because you know it is a Cow
), you have to cast-down to a Cow
, which is painful and absolutely not secure (possible errors at execution time).
There is another solution, which consists in using templates. Give the ColorClass
as a template parameter. The use of this class will define what ColorClass
must do (if something is missing, compilation will fail).
If you want to write the clone
function, define a return_type
that you will use without to know what it is, and which will be Cow
or Dog
etc.
Then there is no need for virtual functions, and you can inline what you want.
class ColorClassRGB { int minLum; double minCos; public: ColorClassRGB(int minLum, double minCos); bool belongs(Pixel pix); }; class ColorClassHSV { int minH, maxH; public: ColorClassHSV(int minH, int maxH); bool belongs(Pixel pix); }; template<class ColorClass> class Vision { public: void init(); void use(Image &img, ColorClass *color); }; //Vision<ColorClassRGB> vision; // one Vision<ColorClassHSV> vision; // or the other void Vision::init() { // ColorClass *color = new ColorClass(20, 0.99); // one ColorClass *color = new ColorClass(20, 50); // or the other use(img, color); } void Vison::use(Image &img, ColorClass *color) { for(int i = 0; i < img.w; i++) for(int j = 0; j < img.h; j++) if (color->belongs(img[i][j])) img[i][j]=Pixel(0,0,0); }
As you can see, the only problem of defining what a class must do by how we use it, is that they should have the same constructor signature, or you will have to modify also the construction to switch between implementations (still, being able to inline functions can be worth it).
Different ways to do some stuff.
Do a loop part again with the same value of the counter.
for(int i = 0; i < n; i++) { if (tab[i] == -1) { swap(tab[i], tab[n-1]); n--; i--; } else do_some_stuff(tab[i]); }
It works. But it won't work in some other cases, if your counter is not just an integer but a pointer scanning a list for example. It's ugly.
A nicer workaround :
bool redo = false; for(int i = 0; i < n; i+=(redo?0:1), redo=false) { if (tab[i] == -1) { swap(tab[i], tab[n-1]); n--; redo = true; } else do_some_stuff(tab[i]); }
Break several nested loops.
for(int i = 0; i < n1; i++) for(int j = 0; j < n2; i++) { if (tab[i][j] == -1) goto break; } break_:
A nicer workaround :
bool break_ = false; for(int i = 0; i < n1 && !break_; i++) for(int j = 0; j < n2 && !break_; j++) { if (tab[i][j] == -1) { break_ = true; break; } }
Do a block of code before returning a function, when there are a lot of return statements in this function.
In this case, as there is no try {} finally {}
block in c/c++, I use the solution with goto
, because workarounds would be really painful (copy the finally block everywhere, or set a flag to jump next steps).
for(int i = 0; i < n; i++) if (tab1[i] = a) goto finally_; for(int i = 0; i < n; i++) if (tab2[i] = a) goto finally_; for(int i = 0; i < n; i++) if (tab3[i] = a) goto finally_; finally_: free(var); return false;
Change the order of execution of two blocks of code according to a variable (without duplication of code of course !).
There is not really ugly and pretty ways. Ok, a pretty way would be for sure to create two functions :
int x_first = 0; if (x_first == 1) { do_for_x(); do_for_y(); } else { do_for_y(); do_for_x(); }
But when the blocks are short, I don't like creating functions called only once because it obliges to scroll when reading.
You could do it with goto
too :
int x_first = 0; int first_done = 0; if (x_first == 1) goto do_for_x; else goto do_for_y; do_for_x: code_for_x... if (first_done == 0) { first_done = true; goto do_for_y; } else goto do_end; do_for_y: code_for_y... if (first_done == 0) { first_done = true; goto do_for_x; } else goto do_end; do_end:
But it's really ugly.
The solution I prefer is to use a loop :
int x_first = 0; for(int k = 0; k <= 1; k++) { if (k == x_first) { code_for_y... } else { code_for_x... } }
You have an abstract class, and several implementations of this class. You have a pointer to the abstract class, but you don't know which implementations it is, and you want to make a copy of this object. The solution is to use a clone
function.
class Descriptor { public: virtual Descriptor* clone() = 0; }; class Type1Descriptor: public Descriptor { public: virtual Descriptor* clone() { return new Type1Descriptor(*this); } }; class AnotherClass { Descriptor *my_descriptor; public: AnotherClass(Descriptor *modelDescriptor) { my_descriptor = modelDescriptor->clone(); } };
If you just want to copy the configuration parameters of the class, you can create a Factory class that will have the same constructor parameters than your class and will store them internally, and that has a create
function that will create a copy of the class with these parameters.
class Descriptor { public: virtual void describe() = 0; Descriptor(int param1, int param2): param1(param1), param2(param2) { internalData = 0; } }; class DescriptorFactory { public: virtual Descriptor* create() const = 0; } class Type1Descriptor: public Descriptor { int param1; int param2; int internalData; public: Descriptor(int param1, int param2): param1(param1), param2(param2) { internalData = 0; } virtual void describe() { ... } }; class Type1DescriptorFactory: public DescriptorFactory { int param1; int param2; public: Descriptor(int param1, int param2): param1(param1), param2(param2) {} virtual Descriptor* create() const { return new Type1Descriptor(param1, param2); } } class AnotherClass { Descriptor *my_descriptor; public: AnotherClass(DescriptorFactory *descriptorFactory) { my_descriptor = descriptorFactory->create(); } };
First see Modular architecture to see why you should maybe use templates instead of inheritance here.
You want two descriptors of same type being able to merge. So you have to declare it in the base class, with the base type, in order to be able to use it in general. But you don't know how to merge two different kinds of descriptors. If you implement a “generic merger” (with base class as parameter) that does nothing, it will be used in priority, even if you overloaded it with inherited class. The only solution is dynamic casting.
class Descriptor { public: virtual void merge(Descriptor* desc) = 0; }; class Type1Descriptor { public: virtual void merge(Descriptor* desc) { Type1Descriptor *desc1 = dynamic_cast<Type1Descriptor>(desc); if (desc1 == NULL) { std::cout << "Error: cannot merge Type1Descriptor with another type of Descriptor" << std::endl; return; } // do the merging of two Type1Descriptor here } };
As there is nothing such as templates in C, it is more difficult to make generic code. But not impossible. There are at least two ways.
Write all your generic code to work with void*
types, and using parameter functions that works with void*
too.
Example:
typedef struct Queue_ { void **elements; int size; int first, last; } Queue; void pushQueue(Queue *q, void *elt) { if (((q->last+1)%(q->size+1)) == q->first) return; // full q->elements[q->last] = elt; q->last = (q->last+1)%(q->size+1); }
Problems:
Define all the necessary types and operations with macros, as well as a macro to give a different name to functions every time the file is included, use them in your generic code, and define them before including the file.
Example:
/* sort.h */ void FNAME(insertionSort)(SORT_TYPE *array, int size) { for(int i = 1; i < size; ++i) { SORT_TYPE val = array[i]; int j = i; while(j > 0 && COMPARE(GET_KEY(val), GET_KEY(array[j-1]))) { array[j] = array[j-1]; --j; } array[j] = val; } } /* somefile.c */ #undef FNAME #undef SORT_TYPE #undef KEY_TYPE #undef GET_KEY #undef COMPARE #define FNAME(name) name##_exval #define SORT_TYPE ExampleTmp* #define KEY_TYPE real #define GET_KEY(x) x->val #define COMPARE(a,b) (a < b) #include "sort.h" #undef FNAME #undef SORT_TYPE #undef KEY_TYPE #undef GET_KEY #undef COMPARE #define FNAME(name) name##_exscore #define SORT_TYPE Example* #define KEY_TYPE real #define GET_KEY(x) x->score #define COMPARE(a,b) (a < b) #include "sort.h" insertionSort_exval(...); insertionSort_exscore(...);