c ++ - Preprocessor - Existential doubt

2

I really have a doubt in existence, and as I go on it makes me more noisy and I really do not know if I'm doing things right. I have my own software, which I developed a few months ago ... it goes quite well in its purpose, but it turns out that I have asked for another one that has the functionality of the previous one but adds additional functionalities ... I mean, the first is based on the second. What happened to me, is about the font game, defining a constant at the project level and adding preprocessor sentences ... in principle it occurred to me that it was the best ... that way I would only change in key places only when I need new classes of the new functionality ... BUT as I go forward I see that it gets complicated and the code becomes in-maintainable. I show parts so they can see what's happening to me.

#include "enginegraph.h"
#include "trackfarmgraph.h"
#ifdef __CORTE_SECCION__
#include "multiworkerdpathgraph.h"
#else
#include "workedpathgraph.h"
#endif
#include "pointsfarmgraph.h"

.... in other parts of the project ...

pointsGraph = NULL;
#ifndef __CORTE_SECCION__
    worker = NULL;
#else
    multiWorker = NULL; 
#endif
engine = NULL;
dlg = NULL;

.... in other parts of the project ...

#ifndef __CORTE_SECCION__
if (worker)
{
    disconnect(worker);
    delete worker;
    worker = NULL;
}
#else
if (multiWorker)
{
    disconnect(multiWorker);
    delete multiWorker;
    multiWorker = NULL;
}
#endif

if (track)
{
    disconnect(track);
    delete track;
    track = NULL;
}

With this I am noticing that the code becomes illegible and with time it will be more. The advantage I find is that if there are bugs in the common part, they are corrected and both software are OK ... but the cost of illegibility and maintenance I think grows exponentially. How else can this be done? THANK YOU

    
asked by Emiliano Torres 27.11.2018 в 15:23
source

2 answers

1

I'll give you a simple example.

We start a repository

  

git init

We add the initial files of the program

  

git add main.cpp Class1.cpp Class1.hpp

     

git commit -m "Initial program"

A client asks us for a very special thing that probably will not be worth the rest. The customer quequismiquis! Well, we created a branch

  

git checkout -b clientetiquismiquis

We add the new files and the modifications of the old ones

  

git add *

     

git commit -m "Version for customer questism list"

Now we jump to our main version to continue improving the program

  

git checkout master

We add improvements to the main branch

  

git add *

     

git commit -m "Added improvements"

And now we want to improve the version of the customer quequismiquis, then we go to his branch:

  

git checkout clientetiquismiquis

and merged with master

  

git merge master

Auto-merging main.cpp
CONFLICT (content): Merge conflict in main.cpp
Automatic merge failed; fix conflicts and then commit the result.

Milks !!! it turns out that you can not merge them automatically, because we open the file in conflict, we leave it well by hand and we follow

  

git add main.cpp

     

git commit -m "Merged master enhancements in clienttiquismiquis"

Everything ready: -)

    
answered by 27.11.2018 / 19:56
source
3

The problem in this case is that you are not isolating the different functions correctly.

Preprocessor directives can bring a lot of flexibility to the code ... but they are far from perfect:

  • The more symbols you define, the more complicated it is to keep the code
  • The code that is outside a #ifdef is not compiled, which forces you to make several compilations to test all combinations (maintenance too expensive)
  • The symbols are not typed, it is easy to get confused and put _CORTE_SECCION__ instead of __CORTE_SECCION__ ... and then spend hours identifying the problem.

It is advisable to isolate the different functionalities in classes or functions. With the new standards, using templates is a very affordable option.

I explain. Let's start with this code fragment:

#ifndef __CORTE_SECCION__
    worker = NULL;
#else
    multiWorker = NULL; 
#endif

// ...

#ifndef __CORTE_SECCION__
if (worker)
{
    disconnect(worker);
    delete worker;
    worker = NULL;
}
#else
if (multiWorker)
{
    disconnect(multiWorker);
    delete multiWorker;
    multiWorker = NULL;
}
#endif

Improving it and isolating the code here is relatively simple.

The first thing I would do would be to define a constant based on __CORTE_SECCION__ :

#ifdef __CORTE_SECCION__
  constexpr bool IsMultiWorker = false;
#else
  constexpr bool IsMultiWorker = true;
#endif

Next we can create a collection of templates for the different use cases (two in this case). Note that you have not indicated the type of worker and multiworker . I will assume that they are Worker and Multiworker .

The case, we are interested in creating a tool that allows us to create a Worker or a Multiworker according to the circumstances:

template<bool>
struct MultiWorkerTraits;

template<>
struct MultiWorkerTraits<false>
{
  using type = Worker;
};

template<>
struct MultiWorkerTraits<true>
{
  using type = MultiWorker;
};

And then, the disconnection functions. As the logic to execute is the same, a single template is enough:

template<class T>
void Disconnect(T & worker)
{
  if( worker )
  {
    disconnect(worker);
    delete worker;
    worker = nullptr;
  }
}

We have all the wicks ready, and we only need to make changes in the original fragment of code:

typename MultiWorkerTraits<IsMultiWorker>::type * worker = nullptr;

// ...

Disconnect(worker);
    
answered by 28.11.2018 в 08:26