Dipende se il tuo problema di versione è un problema in fase di compilazione o in fase di esecuzione.
Ad esempio, potresti voler supportare un formato di messaggio legacy nella tua API. Questo è un problema di runtime perché entrambi i percorsi di codice sono ancora disponibili all'interno dell'applicazione e possono essere chiamati arbitrariamente. Richiede una soluzione run-time: DI, ifs, delegati, ecc. Sono davvero ben coperti dalla risposta di CodesInChaos.
Se vuoi solo costruire una versione precedente del tuo codice per scopi legacy, allora la soluzione è chiaramente una soluzione in fase di compilazione. Anche in una base di codice molto ampia, preferirei comunque dipendere da un sistema di controllo sorgente per questo e non per le condizioni del pre-processore.
Sceglierei questa strada perché, secondo la mia esperienza, il ragionamento che hai menzionato è errato. Non importa in che modo memorizzi le tue molte basi di codice. Che si tratti di file diversi o dello stesso, è necessario mantenere più versioni dello stesso codice. L'unione non dovrebbe essere un problema, in quanto le modifiche alla logica incompatibile non rifletteranno mai a una su una base di codice diversa. Deve essere implementato in modo indipendente. Se lo fa, la tua logica non è incompatibile, il che indica un problema di architettura / fattorizzazione nel tuo codice. Aka, non stai riutilizzando tutto quello che puoi.
Dall'altro lato, occuparsi del doppio del codice in una funzione, separato dalle condizioni del pre-processore senza alcun concetto di scope, trasforma rapidamente il codice in un immenso e illeggibile pasticcio. Si può finire per avere condizioni che attraversano ambiti e l'unico modo per capire cosa sta realmente accadendo è fare da soli il lavoro del pre-processore. Dopo tutto, c'è una ragione per cui lo sviluppatore C ++ considera il pre-processore come a demone che non avrebbe mai dovuto esistere . L'unica soluzione a questo problema è l'utilizzo del tempo di esecuzione se le condizioni impongono l'utilizzo della logica scoped. Eviterei comunque di andare in questo modo perché possono avere un strong impatto negativo sulle prestazioni se vengono utilizzate in un percorso critico.
Alla fine, tutto si riduce a utilizzare lo strumento giusto per il lavoro e uno strumento di sostituzione automatica del testo non è lo strumento giusto per gestire il controllo delle versioni del codice.