Raggiungere la compatibilità futura con C ++ 11


12

Lavoro su una grande applicazione software che deve essere eseguita su più piattaforme. Alcune di queste piattaforme supportano alcune funzionalità di C ++ 11 (ad esempio MSVS 2010) e altre non supportano alcuna (ad esempio GCC 4.3.x). Mi aspetto che questa situazione continui per diversi anni (la mia ipotesi migliore: 3-5 anni).

Detto questo, vorrei impostare un'interfaccia di compatibilità in modo tale che (per quanto possibile) le persone possano scrivere codice C ++ 11 che verrà comunque compilato con compilatori più vecchi con un minimo di manutenzione. Nel complesso, l'obiettivo è ridurre al minimo # ifdef quanto più ragionevolmente possibile, pur consentendo la sintassi / funzionalità di base C ++ 11 sulle piattaforme che li supportano e fornire l'emulazione sulle piattaforme che non lo fanno.

Cominciamo con std :: move (). Il modo più ovvio per ottenere la compatibilità sarebbe inserire qualcosa di simile in un file di intestazione comune:

#if !defined(HAS_STD_MOVE)
namespace std { // C++11 emulation
  template <typename T> inline T& move(T& v) { return v; }
  template <typename T> inline const T& move(const T& v) { return v; }
}
#endif // !defined(HAS_STD_MOVE)

Ciò consente alle persone di scrivere cose come

std::vector<Thing> x = std::move(y);

... con impunità. Fa quello che vogliono in C ++ 11 e fa il meglio che può in C ++ 03. Quando eliminiamo finalmente l'ultimo compilatore C ++ 03, questo codice può rimanere così com'è.

Tuttavia, secondo lo standard, è illegale iniettare nuovi simboli nello stdspazio dei nomi. Questa è la teoria. La mia domanda è: praticamente parlando c'è qualcosa di male nel fare questo come un modo per raggiungere la compatibilità futura?


1
Boost fornisce già un bel po 'di questo, e ha già un codice per usare le nuove funzionalità quando / dove disponibili, quindi potresti essere in grado di usare solo ciò che Boost fornisce e di farcela. Ovviamente ci sono delle limitazioni: la maggior parte delle nuove funzionalità sono state aggiunte specificamente perché le soluzioni basate su libreria non sono adeguate.
Jerry Coffin,

sì, sto pensando specificamente a quelle funzionalità che possono essere implementate a livello di biblioteca, non ai cambiamenti sintattici. Boost in realtà non affronta il problema della compatibilità diretta (continua). A meno che non mi manchi qualcosa ...
mcmcc,

Gcc 4.3 ha già una buona manciata di funzionalità C ++ 11, i riferimenti a Rvalue sono probabilmente i più importanti.
Jan Hudec,

@JanHudec: hai ragione. Scarso esempio. In ogni caso, ci sono altri compilatori che sicuramente non supportano la sintassi (ad es. Qualunque versione del compilatore C ++ di IBM abbiamo).
mcmcc,

Risposte:


9

Ho lavorato per un bel po 'nel mantenere un livello di compatibilità avanti e indietro nei miei programmi C ++, fino a quando alla fine ho dovuto creare un toolkit di libreria , che sto preparando per il rilascio è già stato rilasciato. In generale, purché si accetti che non si otterrà una compatibilità in avanti "perfetta" né nelle funzionalità (alcune cose non possono essere emulate in avanti) non nella sintassi (probabilmente si dovranno usare macro, spazi dei nomi alternativi per alcune cose) allora sei pronto.

Esistono molte funzioni che possono essere emulate in C ++ 03 a un livello sufficiente per l'uso pratico e senza tutte le seccature che ne derivano, ad es .: Boost. Diamine, anche la proposta di standard C ++ nullptrsuggerisce un backport C ++ 03. E poi c'è TR1 per esempio per tutto ciò che riguarda C ++ 11-ma-abbiamo-avuto-anteprime-per-anni. Non solo, alcune caratteristiche di C ++ 14 come asserire varianti, funzioni trasparenti e optional possono essere implementate in C ++ 03!

Le uniche due cose che so che non è assolutamente possibile eseguire il backport sono i modelli constexpr e variadic.

Per quanto riguarda l'intera questione dell'aggiunta di cose allo spazio dei nomi std, la mia opinione è che non importa - affatto. Pensa a Boost, una delle librerie C ++ più importanti e pertinenti e alla loro implementazione di TR1: Boost.Tr1. Se vuoi migliorare il C ++, rendilo compatibile in avanti con C ++ 11, per definizione lo stai trasformando in qualcosa che non è C ++ 03, quindi bloccarti su uno Standard che intendi evitare o lasciare indietro è , in poche parole, controproducente. I puristi si lamenteranno, ma per definizione non è necessario preoccuparsene.

Certo, solo perché non seguirai lo (03) Standard dopo tutto non significa che non puoi provare, o vorrai andare in giro rompendolo. Non è questo il punto. Fintanto che manterrai un controllo molto attento su ciò che viene aggiunto allo stdspazio dei nomi e avrai il controllo degli ambienti in cui viene utilizzato il tuo software (es .: esegui i test!), Non dovrebbe esserci alcun danno non trattabile. Se possibile, definisci tutto in uno spazio dei nomi separato e aggiungi solo usingdirettive allo spazio dei nomi in stdmodo da non aggiungere altro al di là di ciò che "assolutamente" deve avere. Che, IINM, è più o meno ciò che fa Boost.TR1.


Aggiornamento (2013) : come richiesta della domanda originale e vedendo alcuni dei commenti che non posso aggiungere a causa della mancanza di rep, ecco un elenco di funzionalità C ++ 11 e C ++ 14 e il loro grado di portabilità a C ++ 03:

  • nullptr: pienamente attuabile dato il backport ufficiale del Comitato; probabilmente dovrai fornire anche alcune specializzazioni di type_traits in modo che sia riconosciuto come tipo "nativo".
  • forward_list: completamente implementabile, sebbene il supporto dell'allocatore si basi su ciò che può fornire la tua implmenentazione Tr1.
  • Nuovi algoritmi (partition_copy, ecc.): Completamente implementabili.
  • Costruzioni di container da sequenze di parentesi graffe (es . :) vector<int> v = {1, 2, 3, 4};: pienamente implementabili, anche se più vocali di quanto si vorrebbe.
  • static_assert: quasi completamente implementabile quando implementato come macro (dovrai solo stare attento alle virgole).
  • unique_ptr: quasi completamente implementabile, ma avrai anche bisogno del supporto del codice chiamante (per memorizzarlo in contenitori, ecc.); vedi comunque sotto.
  • rvalue-reference: quasi completamente implementabile a seconda di quanto ci si aspetta da loro (es .: Boost Move).
  • Per ogni iterazione: quasi completamente implementabile, la sintassi differirà leggermente.
  • utilizzo delle funzioni locali come argomenti (ad es .: trasformazione): quasi completamente implementabile, ma la sintassi sarà abbastanza diversa - ad esempio, le funzioni locali non sono definite nel sito di chiamata ma subito prima.
  • operatori di conversione esplicita: implementabile a livelli pratici (rendere esplicita la conversione), vedere "esplicito_cast" di C ++ di Imperfect ; ma l'integrazione con funzionalità linguistiche come static_cast<>potrebbe essere quasi impossibile.
  • forwarding di argomenti: implementabile a livelli pratici dato quanto sopra sui riferimenti rvalue, ma dovrai fornire N sovraccarichi alle tue funzioni prendendo argomenti forwardable.
  • mossa: implementabile a livelli pratici (vedi i due aboves). Ovviamente, dovresti usare contenitori e oggetti modificatori per trarne profitto.
  • Allocatori con ambito: non realmente implementabili a meno che l'implementazione di Tr1 non possa essere d'aiuto.
  • tipi di caratteri multibyte: non implementabili se il tuo Tr1 non ti supporta. Ma per lo scopo previsto è meglio fare affidamento su una libreria appositamente progettata per affrontare la questione, come l'ICU, anche se si utilizza C ++ 11.
  • Elenchi di argomenti variabili: implementabili con una certa seccatura, prestare attenzione all'inoltro degli argomenti.
  • noexcept: dipende dalle caratteristiche del tuo compilatore.
  • Nuova autosemantica e decltype: dipende dalle caratteristiche del tuo compilatore - ad es __typeof__. : .
  • tipi interi di dimensioni ( int16_t, ecc.): dipende dalle funzionalità del compilatore oppure è possibile delegare a Portable stdint.h.
  • attributi di tipo: dipende dalle caratteristiche del tuo compilatore.
  • Elenco di inizializzatori: non implementabile a mia conoscenza; tuttavia, se si desidera inizializzare i contenitori con sequenze, vedere quanto sopra in "costruzioni di contenitori".
  • Alias ​​dei modelli: non implementabile per quanto ne sappia, ma è comunque una funzione non necessaria e ::typenei modelli abbiamo sempre avuto
  • Modelli variabili: non implementabili a mia conoscenza; la chiusura è l'impostazione predefinita dell'argomento template, che richiede N specializzazioni, ecc.
  • constexpr: Non implementabile per quanto ne so.
  • Inizializzazione uniforme: non implementabile per quanto ne sappia , ma l' inizializzazione predefinita del costruttore può essere implementata come valore inizializzato da Boost.
  • C ++ 14 dynarray: completamente implementabile.
  • C ++ 14 optional<>: quasi completamente implementabile a condizione che il compilatore C ++ 03 supporti le configurazioni di allineamento.
  • Funzioni trasparenti C ++ 14: quasi completamente implementabili, ma il codice client probabilmente dovrà usare esplicitamente, ad esempio: std::less<void>per farlo funzionare.
  • C ++ 14 nuove varianti di assert (come assure): completamente implementabili se si desidera assert, quasi completamente implementabili se si desidera abilitare i tiri.
  • Estensioni di tupla in C ++ 14 (ottieni l'elemento tupla per tipo): completamente implementabile e puoi persino far sì che non riesca a compilare con i casi esatti descritti nella proposta di funzionalità.

(Dichiarazione di non responsabilità: molte di queste funzionalità sono implementate nella mia libreria di backport C ++ che ho collegato sopra, quindi penso di sapere di cosa sto parlando quando dico "pienamente" o "quasi completamente".)


6

Questo è fondamentalmente impossibile. Prendere in considerazione std::unique_ptr<Thing>. Se fosse possibile emulare i riferimenti rvalue come libreria, non sarebbe una funzione linguistica.


1
Ho detto "per quanto possibile". Chiaramente alcune funzionalità dovranno essere lasciate indietro # ifdef o non essere utilizzate affatto. std :: move () sembra essere uno che puoi supportare la sintassi (anche se non la funzionalità).
mcmcc,

2
In realtà la proposta di riferimenti rvalue menziona una soluzione basata su libreria!
Jan Hudec,

Più specificamente, è possibile implementare la semantica di spostamento per una particolare classe in C ++ 03, quindi dovrebbe essere possibile definirla std::unique_ptrlì, ma alcune altre caratteristiche dei riferimenti ai valori non possono essere implementate in C ++ 03, quindi std::forwardnon è possibile. L'altra cosa è che std::unique_ptrnon sarà utile, perché le raccolte non useranno la semantica di movimento a meno che non le sostituiate tutte.
Jan Hudec,

@JanHudec: non è possibile definire unique_ptr. Guarda i fallimenti di auto_ptr. unique_ptrè praticamente l' esempio da manuale di una classe la cui semantica era fondamentalmente abilitata dalla funzione linguistica.
DeadMG

@DeadMG: No, non è unique_ptrstato fondamentalmente abilitato dalla funzione lingua. Tuttavia non sarebbe molto utile senza quella funzionalità. perché senza un inoltro perfetto non sarebbe utilizzabile in molti casi e l'inoltro perfetto non richiede questa funzionalità.
Jan Hudec,

2
  1. Gcc ha iniziato a introdurre C ++ 11 (ancora C ++ 0x in quel momento) in 4.3. Questa tabella dice che ha già riferimenti a valori e alcune altre funzionalità meno utilizzate (devi specificare l' -std=c++0xopzione per abilitarle).
  2. Molte aggiunte alla libreria standard in C ++ 11 erano già definite in TR1 e GNU stdlibc ++ le fornisce nello spazio dei nomi std :: tr1. Quindi fai solo un uso condizionale appropriato.
  3. Boost definisce la maggior parte delle funzioni di TR1 e può inserirle nello spazio dei nomi di TR1 se non lo possiedi (ma VS2010 lo fa e gcc 4.3 lo fa anche se usi GNU stdlibc ++).
  4. Mettere qualsiasi cosa nello stdspazio dei nomi è un "comportamento indefinito". Ciò significa che la specifica non dice cosa accadrà. Ma se sai che su una particolare piattaforma la libreria standard non definisce qualcosa, vai avanti e definiscila. Aspettati solo che dovrai controllare su ogni piattaforma ciò di cui hai bisogno e cosa puoi definire.
  5. La proposta di riferimenti rvalue, N1690 cita come implementare la semantica spostare in C ++ 03. Potrebbe essere usato per sostituire unique_ptr. Tuttavia, non sarebbe troppo utile, perché si basa sulle raccolte che utilizzano effettivamente la semantica di spostamento e quelle C ++ 03 ovviamente no.

1
Hai ragione su GCC, ma sfortunatamente, devo anche supportare altri compilatori (non GCC). Il tuo proiettile n. 4 è al centro della domanda che sto ponendo. # 5 è interessante, ma non sto cercando di supportare la semantica di spostamento (l'ottimizzazione della copia) su queste piattaforme più vecchie, ma piuttosto "std :: move ()" come sintassi compilabile.
mcmcc,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.