C++ Logica condizionale e gestione multipiattaforma


Esempio

In breve, la logica di pre-elaborazione condizionale riguarda la possibilità di rendere la logica del codice disponibile o non disponibile per la compilazione utilizzando le definizioni di macro.

Tre casi d'uso importanti sono:

  • diversi profili di app (ad es. debug, release, test, ottimizzati) che possono essere candidati alla stessa app (ad esempio con registrazione aggiuntiva).
  • cross-platform compila - codice di base singola, piattaforme di compilazione multiple.
  • utilizzando una base di codice comune per più versioni dell'applicazione (es. versioni Basic, Premium e Pro di un software) - con caratteristiche leggermente diverse.

Esempio a: un approccio multipiattaforma per la rimozione dei file (illustrativo):

#ifdef _WIN32
#include <windows.h> // and other windows system files
#endif
#include <cstdio>

bool remove_file(const std::string &path) 
{
#ifdef _WIN32
  return DeleteFile(path.c_str());
#elif defined(_POSIX_VERSION) || defined(__unix__)
  return (0 == remove(path.c_str()));
#elif defined(__APPLE__)
  //TODO: check if NSAPI has a more specific function with permission dialog
  return (0 == remove(path.c_str()));
#else 
#error "This platform is not supported"
#endif
}

Macro come _WIN32 , __APPLE__ o __unix__ sono normalmente predefinite dalle corrispondenti implementazioni.

Esempio b: abilitazione della registrazione aggiuntiva per una build di debug:

void s_PrintAppStateOnUserPrompt()
{
    std::cout << "--------BEGIN-DUMP---------------\n"
              << AppState::Instance()->Settings().ToString() << "\n"
#if ( 1 == TESTING_MODE ) //privacy: we want user details only when testing
              << ListToString(AppState::UndoStack()->GetActionNames())
              << AppState::Instance()->CrntDocument().Name() 
              << AppState::Instance()->CrntDocument().SignatureSHA() << "\n"
#endif
              << "--------END-DUMP---------------\n"
}

Esempio c: abilitare una funzione premium in una build di prodotto separata (nota: questo è illustrativo, spesso è un'idea migliore consentire di sbloccare una funzionalità senza la necessità di reinstallare un'applicazione)

void MainWindow::OnProcessButtonClick()
{
#ifndef _PREMIUM
    CreatePurchaseDialog("Buy App Premium", "This feature is available for our App Premium users. Click the Buy button to purchase the Premium version at our website");
    return;
#endif
    //...actual feature logic here
}

Alcuni trucchi comuni:

Definizione dei simboli al momento del richiamo:

Il preprocessore può essere richiamato con simboli predefiniti (con inizializzazione opzionale). Ad esempio questo comando ( gcc -E esegue solo il preprocessore)

gcc -E -DOPTIMISE_FOR_OS_X -DTESTING_MODE=1 Sample.cpp

elabora Sample.cpp nello stesso modo in cui sarebbe se #define OPTIMISE_FOR_OS_X e #define TESTING_MODE 1 venissero aggiunti all'inizio di Sample.cpp.

Garantire una macro è definita:

Se una macro non è definita e il suo valore viene confrontato o verificato, il preprocessore assume quasi sempre in silenzio il valore di 0 . Ci sono alcuni modi per lavorare con questo. Un approccio è quello di assumere che le impostazioni predefinite siano rappresentate come 0 e che qualsiasi modifica (ad esempio al profilo di build dell'app) debba essere esplicitamente eseguita (ad es. ENABLE_EXTRA_DEBUGGING = 0 per impostazione predefinita, set -DENABLE_EXTRA_DEBUGGING = 1 per eseguire l'override). Un altro approccio è rendere esplicite tutte le definizioni e le impostazioni predefinite. Questo può essere ottenuto utilizzando una combinazione di direttive #ifndef e #error :

#ifndef (ENABLE_EXTRA_DEBUGGING)
// please include DefaultDefines.h if not already included.
#    error "ENABLE_EXTRA_DEBUGGING is not defined"
#else
#    if ( 1 == ENABLE_EXTRA_DEBUGGING )
  //code
#    endif
#endif