Quali modi di dire C ++ sono deprecati in C ++ 11?


192

Con il nuovo standard, ci sono nuovi modi di fare le cose, e molti sono più belli dei vecchi, ma il vecchio va ancora bene. È anche chiaro che il nuovo standard non si deprezza molto ufficialmente, per motivi di compatibilità con le versioni precedenti. Quindi la domanda che rimane è:

Quali vecchi modi di codificare sono decisamente inferiori agli stili C ++ 11, e cosa possiamo fare ora invece?

Nel rispondere a questo, puoi saltare le cose ovvie come "usa le variabili automatiche".


13
Non puoi deprecare gli idiomi.
Pubblicazione

6
Il discorso di Herb Sutter a Going Native 2012 ha riguardato questo:
bames53,

5
Restituire valori costanti non è più incoraggiato. Ovviamente auto_ptrè anche deprecato.
Kerrek SB,

27
Certo che puoi, Pubby. Prima dell'invenzione dei modelli C ++, esisteva una tecnica macro per creare modelli. Quindi il C ++ li ha aggiunti e il vecchio modo era considerato cattivo.
Alan Baljeu,

7
Questa domanda deve davvero essere spostata su Programmers.se.
Nicol Bolas,

Risposte:


173
  1. Classe finale : C ++ 11 fornisce l' finalidentificatore per impedire la derivazione della classe
  2. C ++ 11 lambdas riduce sostanzialmente la necessità di classi di oggetti funzione (functor) denominati.
  3. Costruttore di movimento : i modi magici in cui le std::auto_ptropere non sono più necessarie a causa del supporto di prima classe per i riferimenti di valore.
  4. Bool sicuro : questo è stato menzionato in precedenza. Gli operatori espliciti di C ++ 11 ovviano a questo linguaggio C ++ 03 molto comune.
  5. Riducibile : molti contenitori C ++ 11 STL forniscono ashrink_to_fit() funzione membro, che dovrebbe eliminare la necessità di scambiare con un temporaneo.
  6. Classe di base temporanea : alcune vecchie librerie C ++ usano questo linguaggio piuttosto complesso. Con la semantica di spostamento non è più necessario.
  7. Le enumerazioni Enum di tipo sicuro sono molto sicure in C ++ 11.
  8. Proibire l'allocazione dell'heap : la = deletesintassi è un modo molto più diretto per dire che una particolare funzionalità viene esplicitamente negata. Ciò è applicabile per impedire l'allocazione dell'heap (ovvero, =deleteper i membri operator new), impedire copie, assegnazioni, ecc.
  9. Typedef con template : i template alias in C ++ 11 riducono la necessità di semplici typedef con template. Tuttavia, i generatori di tipi complessi hanno ancora bisogno di meta funzioni.
  10. Alcuni calcoli numerici in fase di compilazione, come Fibonacci, possono essere facilmente sostituiti usando espressioni costanti generalizzate
  11. result_of: Gli usi del modello di classe result_ofdevono essere sostituiti con decltype. Penso che result_ofusi decltypequando è disponibile.
  12. Gli inizializzatori membri in classe salvano la digitazione per l'inizializzazione predefinita dei membri non statici con valori predefiniti.
  13. Nel nuovo codice C ++ 11 NULLdovrebbe essere ridefinito come nullptr, ma vedi i discorsi di STL per scoprire perché hanno deciso di non farlo.
  14. I fanatici dei modelli di espressioni sono lieti di avere la sintassi della funzione del tipo di ritorno finale in C ++ 11. Niente più tipi di ritorno a 30 righe!

Penso che mi fermerò qui!


Grazie per le cose dettagliate!
Alan Baljeu,

7
Ottima risposta, ma vorrei colpire result_ofdalla lista. Nonostante l'ingombrante typenamenecessità prima, penso che a typename result_of<F(Args...)::typevolte possa essere più facile da leggere rispetto a decltype(std::declval<F>()(std::declval<Args>()...), e con l'accettazione di N3436 nel documento di lavoro entrambi lavorano per SFINAE (che era un vantaggio decltypeche result_ofnon offriva)
Jonathan Wakely

Riguardo a 14) Sto ancora piangendo che devo usare le macro per scrivere lo stesso codice due volte - una volta per il corpo della funzione e una volta per l'istruzione decltype () ...

2
Vorrei sottolineare che questo argomento è collegato da questa pagina Microsoft come un articolo "Per ulteriori informazioni" in un'introduzione generale al linguaggio C ++, ma questo argomento è altamente specializzato! Posso suggerire che un breve "Questo argomento NON è per i principianti C ++!" consigli da includere all'inizio dell'argomento o questa risposta?
Aacini,

Ri 12: "Inizializzazione dei membri in classe": questo è il nuovo linguaggio, non un linguaggio deprecato, vero? Cambiare l'ordine delle frasi forse? Ri 2: I Functor sono molto utili quando si desidera passare intorno ai tipi piuttosto che agli oggetti (specialmente nei parametri del modello). Quindi sono solo alcuni usi di funzioni che sono deprecati.
einpoklum,

66

Ad un certo punto si è sostenuto che si dovrebbe tornare per constvalore anziché semplicemente per valore:

const A foo();
^^^^^

Questo era per lo più innocuo in C ++ 98/03 e potrebbe anche aver catturato alcuni bug che sembravano:

foo() = a;

Ma tornare da constè controindicato in C ++ 11 perché inibisce la semantica di movimento:

A a = foo();  // foo will copy into a instead of move into it

Quindi rilassati e codifica:

A foo();  // return by non-const value

9
Gli errori evitabili possono tuttavia essere ora rilevati utilizzando i qualificatori di riferimento per le funzioni. Come nel caso precedente che definisce A& operator=(A o)&invece di A& operator=(A o). Questi impediscono gli errori sciocchi e fanno sì che le classi si comportino più come i tipi di base e non impediscono la semantica dei movimenti.
Joe

61

Non appena puoi abbandonare 0e NULLin favore di nullptr, fallo!

Nel codice non generico l'uso di 0o NULLnon è un grosso problema. Ma non appena si inizia a passare intorno a costanti puntatore null nel codice generico, la situazione cambia rapidamente. Quando si passa 0a a template<class T> func(T) Tviene dedotto come intcostante del puntatore null e non. E dopo ciò non può essere riconvertito in una costante puntatore null. Ciò si riversa in un pantano di problemi che semplicemente non esistono se l'universo usasse solo nullptr.

C ++ 11 non viene deprecato 0e NULLcome costanti puntatore null. Ma dovresti programmare come se fosse così.


che cos'è decltype (nullptr)?

4
@GrapschKnutsch: Lo è std::nullptr_t.
Howard Hinnant,

Suggerire che questo sia riformulato come idioma deprecato piuttosto che la nuova convenzione da adottare (ad es. "L'uso di 0o NULLper puntatori nulli").
einpoklum,

38

Idioma sicuro boolexplicit operator bool().

Costruttori di copie private (boost :: non copiabile) → X(const X&) = delete

Simulazione della classe finale con distruttore privato ed eredità virtualeclass X final


esempi buoni e concisi, uno dei quali reca persino la parola "linguaggio" in esso. ben messo
Sebastian Mach,

2
Wow, non ho mai visto il "linguaggio sicuro bool" prima, sembra abbastanza disgustoso! Spero di non averne mai bisogno nel codice pre-C ++ 11 ...
boycy il

24

Una delle cose che ti fanno solo evitare di scrivere algoritmi di base in C ++ 11 è la disponibilità di lambda in combinazione con gli algoritmi forniti dalla libreria standard.

Sto usando quelli ora ed è incredibile quante volte dici semplicemente cosa vuoi fare usando count_if (), for_each () o altri algoritmi invece di dover scrivere di nuovo i dannati loop.

Una volta che stai usando un compilatore C ++ 11 con una libreria standard C ++ 11 completa, non hai più buone scuse per non usare algoritmi standard per costruire il tuo . Lambda lo uccide e basta.

Perché?

In pratica (dopo aver usato questo modo di scrivere algoritmi da solo) è molto più facile leggere qualcosa che è costruito con parole semplici che significano ciò che viene fatto che con alcuni cicli che devi decifrare per conoscerne il significato. Detto questo, la deduzione automatica degli argomenti lambda aiuterebbe molto a rendere la sintassi più facilmente paragonabile a un ciclo non elaborato.

Fondamentalmente, leggere gli algoritmi realizzati con algoritmi standard è molto più semplice in quanto le parole nascondono i dettagli di implementazione dei loop.

Immagino che solo gli algoritmi di livello superiore debbano essere pensati ora che abbiamo algoritmi di livello inferiore su cui basarci.


8
In realtà c'è una buona scusa. Stai usando gli algoritmi di Boost.Range , che sono molto più belli;)
Nicol Bolas,

10
Non vedo che for_eachcon un lambda sia meglio dell'equivalente range-based per loop, con il contenuto del lambda nel loop. Il codice sembra più o meno lo stesso, ma lambda introduce qualche punteggiatura aggiuntiva. Puoi usare equivalenti di cose come boost::irangeapplicarlo a più loop rispetto a quelli che ovviamente usano iteratori. Inoltre il range-based per loop ha una maggiore flessibilità, in quanto puoi uscire presto se richiesto (da returno da break), mentre con for_eachte avresti bisogno di lanciare.
Steve Jessop,

5
@SteveJessop: Anche così, la disponibilità di range-based forrende it = c.begin(), const end = c.end(); it != end; ++itdefunto il solito linguaggio.
Ben Voigt,

7
@SteveJessop Un vantaggio for_eachdell'algoritmo nell'intervallo basato su loop è che non puoi break o return. Cioè, quando vedi for_eachche sai immediatamente senza guardare il corpo che non c'è tale inganno.
bames53,

5
@Klaim: per essere precisi, sto confrontando ad esempio std::for_each(v.begin(), v.end(), [](int &i) { ++i; });con for (auto &i : v) { ++i; }. Accetto che la flessibilità sia a doppio taglio ( gotoè molto flessibile, questo è il problema). Non credo che il vincolo di non essere in grado di utilizzare breakle for_eachcompensa versione per la prolissità in più richiede - gli utenti di for_eachqui sono IMO sacrificare la leggibilità reale e convenienza per una sorta di concezione teorica che il for_eachè in linea di principio più chiaro e concettualmente più semplice. In pratica non è più chiaro o più semplice.
Steve Jessop,

10

Dovrai implementare versioni personalizzate con swapminore frequenza. In C ++ 03, swapè spesso necessario un non-lancio efficiente per evitare copie costose e da lancio, e poiché std::swaputilizza due copie, swapspesso deve essere personalizzato. In C ++, std::swaputilizza move, e quindi l'attenzione si sposta sull'implementazione di costruttori di mosse efficienti e senza lancio e sugli operatori di assegnazione di mosse. Dal momento che per questi il ​​valore predefinito spesso va bene, questo sarà molto meno lavoro che in C ++ 03.

Generalmente è difficile prevedere quali modi di dire verranno utilizzati dal momento che sono stati creati attraverso l'esperienza. Possiamo aspettarci un "efficace C ++ 11" forse il prossimo anno e uno "standard di codifica C ++ 11" solo tra tre anni perché l'esperienza necessaria non è ancora arrivata.


1
Ne dubito. Lo stile consigliato è utilizzare lo scambio per spostare e copiare la costruzione, ma non std :: swap perché sarebbe circolare.
Alan Baljeu,

Sì, ma il costruttore di mosse di solito chiama uno scambio personalizzato, o è sostanzialmente equivalente.
Inverso,

2

Non ne conosco il nome, ma il codice C ++ 03 spesso utilizzava il seguente costrutto in sostituzione dell'assegnazione di spostamento mancante:

std::map<Big, Bigger> createBigMap(); // returns by value

void example ()
{
  std::map<Big, Bigger> map;

  // ... some code using map

  createBigMap().swap(map);  // cheap swap
}

Ciò ha evitato qualsiasi copia a causa dell'elisione della copia combinata con quanto swapsopra.


1
Nel tuo esempio lo swap non è necessario, la copia elisione comporterebbe comunque il valore di ritorno map. La tecnica che mostri è utile se mapesiste già, piuttosto che essere semplicemente costruita. L'esempio sarebbe migliore senza il commento "costruttore predefinito economico" e con "// ..." tra quella costruzione e lo scambio
Jonathan Wakely

L'ho cambiato secondo il tuo suggerimento. Grazie.
Andrzej,

L'uso di "big" e "Bigger" è confuso. Perché non spiegare come contano le dimensioni della chiave e il tipo di valore?
einpoklum,

1

Quando ho notato che un compilatore che utilizza lo standard C ++ 11 non presenta più il seguente codice:

std::vector<std::vector<int>> a;

per presumibilmente contenente operatore >>, ho iniziato a ballare. Nelle versioni precedenti si dovrebbe fare

std::vector<std::vector<int> > a;

A peggiorare le cose, se hai mai dovuto eseguire il debug di questo, sai quanto sono orribili i messaggi di errore che ne derivano.

Tuttavia, non so se questo fosse "ovvio" per te.


1
Questa funzionalità è stata aggiunta già nel C ++ precedente. O almeno Visual C ++ lo ha implementato per discussione sugli standard molti anni prima.
Alan Baljeu,

1
@AlanBaljeu Naturalmente, ci sono molte cose non standard aggiunte al compilatore / librerie. C'erano tonnellate di compilatori che avevano una dichiarazione di variabile "auto" prima di C ++ 11, ma non potevi essere sicuro che il tuo codice potesse essere effettivamente compilato da qualsiasi altra cosa. La domanda riguardava lo standard, non "c'era un compilatore che potesse farlo".
v010dya,

1

Il ritorno in base al valore non è più un problema. Con la semantica di spostamento e / o l'ottimizzazione del valore di ritorno (dipendente dal compilatore) le funzioni di codifica sono più naturali senza costi generali o costi (il più delle volte).


... ma quale linguaggio è stato deprecato?
einpoklum,

Non un idioma ma era una buona pratica non più necessaria. Anche con RVO supportato dal compilatore che è facoltativo. en.wikipedia.org/wiki/Return_value_optimization "Nelle prime fasi dell'evoluzione del C ++, l'incapacità del linguaggio di restituire efficacemente un oggetto di tipo classe da una funzione era considerata un punto debole ....." struct Data {char bytes [ 16]; }; void f (Data * p) {// genera il risultato direttamente in * p} int main () {Data d; f (& d); }
Martin A

Stavo suggerendo che dovresti pronunciare la tua risposta come "l'abitudine di evitare il ritorno in base al valore non è più pertinente come ecc. Ecc. Ecc."
einpoklum,
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.