Quels idiomes C ++ sont obsolètes dans C ++ 11?


192

Avec la nouvelle norme, il existe de nouvelles façons de faire les choses, et beaucoup sont plus agréables que les anciennes méthodes, mais l'ancienne méthode est toujours bonne. Il est également clair que la nouvelle norme n'est pas officiellement obsolète, pour des raisons de compatibilité descendante. La question qui reste est donc:

Quelles anciennes méthodes de codage sont définitivement inférieures aux styles C ++ 11, et que pouvons-nous faire maintenant à la place?

En répondant à cela, vous pouvez sauter les choses évidentes comme "utiliser des variables automatiques".


13
Vous ne pouvez pas désapprouver les idiomes.
Pubby

6
La conférence de Herb Sutter à Going Native 2012 a couvert ceci:
bames53

5
Le renvoi de valeurs constantes n'est plus encouragé. De toute évidence, il auto_ptrest également obsolète.
Kerrek SB

27
Bien sûr que vous pouvez, Pubby. Avant l'invention des modèles C ++, il existait une technique de macro pour créer des modèles. Puis C ++ les a ajoutés, et l'ancienne méthode était considérée comme mauvaise.
Alan Baljeu

7
Cette question doit vraiment être déplacée vers Programmers.se.
Nicol Bolas

Réponses:


173
  1. Classe finale : C ++ 11 fournit le finalspécificateur pour empêcher la dérivation de classe
  2. Les lambdas C ++ 11 réduisent considérablement le besoin de classes d'objets de fonction nommés (foncteurs).
  3. Move Constructor : les façons magiques dont les std::auto_ptrtravaux ne sont plus nécessaires en raison du support de première classe des références rvalue.
  4. Safe bool : cela a été mentionné plus tôt. Les opérateurs explicites de C ++ 11 évitent cet idiome très courant de C ++ 03.
  5. Réduire à l'ajustement : de nombreux conteneurs C ++ 11 STL fournissent une shrink_to_fit()fonction membre, ce qui devrait éliminer le besoin d'échanger avec un fichier temporaire.
  6. Classe de base temporaire : certaines anciennes bibliothèques C ++ utilisent cet idiome plutôt complexe. Avec la sémantique de déplacement, ce n'est plus nécessaire.
  7. Les énumérations de type Safe Enum sont très sûres dans C ++ 11.
  8. Interdire l'allocation de tas : la = deletesyntaxe est une manière beaucoup plus directe de dire qu'une fonctionnalité particulière est explicitement refusée. Ceci est applicable pour empêcher l'allocation de tas (c'est-à-dire =deletepour le membre operator new), empêcher les copies, l'affectation, etc.
  9. Typedef basé sur un modèle : modèles Alias en C ++ 11 réduire la nécessité d'typedefs de simples basé sur un modèle. Cependant, les générateurs de types complexes ont toujours besoin de méta-fonctions.
  10. Certains calculs numériques au moment de la compilation, tels que Fibonacci, peuvent être facilement remplacés à l'aide d' expressions constantes généralisées
  11. result_of: Les utilisations du modèle de classe result_ofdoivent être remplacées par decltype. Je pense result_ofutilise decltypequand il est disponible.
  12. Les initialiseurs de membres en classe enregistrent la saisie pour l'initialisation par défaut des membres non statiques avec des valeurs par défaut.
  13. Dans le nouveau C ++ 11, le code NULLdevrait être redéfini comme nullptr, mais voyez le discours de STL pour savoir pourquoi ils ont décidé de ne pas le faire.
  14. Les fanatiques de modèles d'expression sont ravis d'avoir la syntaxe de la fonction de type retour de fin en C ++ 11. Fini les types de retour de 30 lignes!

Je pense que je vais m'arrêter là!


Merci pour les détails!
Alan Baljeu

7
Excellente réponse, mais je supprimerais result_ofde la liste. Malgré la lourdeur typenamenécessaire avant cela, je pense que cela typename result_of<F(Args...)::typepeut parfois être plus facile à lire que decltype(std::declval<F>()(std::declval<Args>()...), et avec l'acceptation de N3436 dans le document de travail, ils travaillent tous les deux pour SFINAE (qui était autrefois un avantage de decltypecela result_ofn'a pas offert)
Jonathan Wakely

Concernant 14) Je pleure toujours que je dois utiliser des macros pour écrire deux fois le même code - une fois pour le corps de la fonction et une fois pour l'instruction decltype () ...

2
Je voudrais noter que ce sujet est lié à partir de cette page Microsoft en tant qu'article «Pour plus d'informations» dans une introduction générale au langage C ++, mais ce sujet est hautement spécialisé! Puis-je suggérer qu'un bref "Ce sujet n'est PAS pour les novices en C ++!" des conseils doivent-ils figurer au début du sujet ou de cette réponse?
Aacini

Re 12: "Initialisation des membres en classe" - c'est le nouvel idiome, pas un idiome obsolète, n'est-ce pas? Changer l'ordre des phrases peut-être? Re 2: Les foncteurs sont très utiles lorsque vous souhaitez passer des types plutôt que des objets (en particulier dans les paramètres de modèle). Donc, seules certaines utilisations des foncteurs sont obsolètes.
einpoklum

66

À un moment donné, on a fait valoir qu'il fallait renvoyer par constvaleur au lieu de simplement par valeur:

const A foo();
^^^^^

C'était pour la plupart inoffensif en C ++ 98/03, et peut même avoir détecté quelques bogues qui ressemblaient à:

foo() = a;

Mais le retour par constest contre-indiqué en C ++ 11 car il inhibe la sémantique de déplacement:

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

Alors détendez-vous et codez:

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

9
Les erreurs évitables peuvent cependant maintenant être détectées en utilisant des qualificatifs de référence pour les fonctions. Comme dans le cas ci-dessus définissant A& operator=(A o)&au lieu de A& operator=(A o). Ceux-ci évitent les erreurs stupides et font que les classes se comportent davantage comme des types de base et n'empêchent pas la sémantique de déplacement.
Joe

61

Dès que vous pouvez abandonner 0et NULLen faveur de nullptr, faites-le!

Dans le code non générique, l'utilisation de 0ou NULLn'est pas si grave. Mais dès que vous commencez à transmettre des constantes de pointeur nulles dans du code générique, la situation change rapidement. Lorsque vous passez 0à a, il template<class T> func(T) Test déduit comme un intet non comme une constante de pointeur nul. Et il ne peut pas être reconverti en une constante de pointeur nul après cela. Cela tombe en cascade dans un bourbier de problèmes qui n'existent tout simplement pas si l'univers est utilisé uniquement nullptr.

C ++ 11 n'est pas obsolète 0et en NULLtant que constantes de pointeur nul. Mais vous devez coder comme si c'était le cas.


qu'est-ce que decltype (nullptr)?

4
@GrapschKnutsch: C'est vrai std::nullptr_t.
Howard Hinnant

Suggérez que cela soit reformulé comme l'idiome obsolète plutôt que comme la nouvelle convention à adopter (par exemple «L'utilisation de 0ou NULLpour des pointeurs nuls»).
einpoklum

38

Idiome booléen sûrexplicit operator bool().

Constructeurs de copie privée (boost :: noncopyable) → X(const X&) = delete

Simulation de la classe finale avec destructeur privé et héritage virtuelclass X final


des exemples bons et concis, dont l'un porte même le mot «idiome». well put
Sebastian Mach

2
Wow, je n'ai jamais vu l'idiome «safe bool» avant, ça a l'air assez dégoûtant! J'espère ne jamais en avoir besoin dans le code pré-C ++ 11 ...
boycy

24

L'une des choses qui vous évite d'écrire des algorithmes de base en C ++ 11 est la disponibilité des lambdas en combinaison avec les algorithmes fournis par la bibliothèque standard.

J'utilise ceux-ci maintenant et c'est incroyable à quelle fréquence vous dites simplement ce que vous voulez faire en utilisant count_if (), for_each () ou d'autres algorithmes au lieu d'avoir à réécrire les foutues boucles.

Une fois que vous utilisez un compilateur C ++ 11 avec une bibliothèque standard C ++ 11 complète, vous n'avez plus de bonne excuse pour ne pas utiliser d'algorithmes standard pour construire le vôtre . Lambda juste le tuer.

Pourquoi?

En pratique (après avoir moi-même utilisé cette façon d'écrire des algorithmes), il semble beaucoup plus facile de lire quelque chose qui est construit avec des mots simples signifiant ce qui est fait qu'avec des boucles qu'il faut déchiffrer pour en connaître le sens. Cela dit, faire déduire automatiquement des arguments lambda aiderait beaucoup à rendre la syntaxe plus facilement comparable à une boucle brute.

Fondamentalement, la lecture d'algorithmes réalisés avec des algorithmes standard est beaucoup plus facile car les mots masquent les détails d'implémentation des boucles.

Je suppose que seuls les algorithmes de niveau supérieur doivent être considérés maintenant que nous avons des algorithmes de niveau inférieur sur lesquels nous appuyer.


8
En fait, il y a une bonne excuse. Vous utilisez les algorithmes de Boost.Range , qui sont beaucoup plus agréables;)
Nicol Bolas

10
Je ne vois pas for_eachqu'avec un lambda, c'est mieux que la boucle for équivalente basée sur la plage, avec le contenu du lambda dans la boucle. Le code a plus ou moins le même aspect, mais le lambda introduit une ponctuation supplémentaire. Vous pouvez utiliser des équivalents de choses comme l' boost::irangeappliquer à plus de boucles que celles qui utilisent évidemment des itérateurs. De plus, la boucle for basée sur la plage a une plus grande flexibilité, en ce sens que vous pouvez quitter tôt si nécessaire (par returnou par break), alors qu'avec for_eachvous auriez besoin de lancer.
Steve Jessop

5
@SteveJessop: Même ainsi, la disponibilité de la fonction basée sur la plage forfait disparaître l' it = c.begin(), const end = c.end(); it != end; ++itidiome habituel .
Ben Voigt

7
@SteveJessop Un avantage de l' for_eachalgorithme sur la plage basée sur la boucle for est que vous ne pouvez pas break ou return. Autrement dit, lorsque vous voyez, for_eachvous savez immédiatement sans regarder le corps qu'il n'y a pas une telle ruse.
bames53

5
@Klaim: pour être précis, je compare par exemple std::for_each(v.begin(), v.end(), [](int &i) { ++i; });avec for (auto &i : v) { ++i; }. J'accepte que la flexibilité soit à double tranchant ( gotoc'est très flexible, c'est le problème). Je ne pense pas que la contrainte de ne pas pouvoir utiliser breakdans la for_eachversion compense la verbosité supplémentaire qu'elle exige - les utilisateurs d' for_eachici sont l'OMI sacrifiant la lisibilité et la commodité réelles pour une sorte de notion théorique qui for_eachest en principe plus claire et conceptuellement plus simple. En pratique, ce n'est ni plus clair ni plus simple.
Steve Jessop

10

Vous devrez implémenter des versions personnalisées swapmoins souvent. En C ++ 03, un non-throwing efficace swapest souvent nécessaire pour éviter des copies coûteuses et lancées, et comme std::swaputilise deux copies, swapil doit souvent être personnalisé. En C ++, std::swaputilisemove , et donc l'accent est mis sur l'implémentation de constructeurs de déplacement et d'opérateurs d'affectation de déplacement efficaces et non lanceurs. Comme pour ceux-ci, la valeur par défaut est souvent très bien, ce sera beaucoup moins de travail qu'en C ++ 03.

En général, il est difficile de prédire quels idiomes seront utilisés car ils sont créés par l'expérience. On peut s'attendre à un «C ++ 11 efficace» peut-être l'année prochaine, et à des «normes de codage C ++ 11» seulement dans trois ans car l'expérience nécessaire n'est pas encore là.


1
J'en doute. Le style recommandé est d'utiliser swap pour la construction de déplacement et de copie, mais pas std :: swap car ce serait circulaire.
Alan Baljeu

Ouais, mais le constructeur de mouvement appelle généralement un échange personnalisé, ou c'est essentiellement équivalent.
Inverse le

2

Je ne connais pas le nom, mais le code C ++ 03 utilisait souvent la construction suivante en remplacement de l'affectation de déplacement manquante:

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

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

  // ... some code using map

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

Cela a évité toute copie en raison de l'élision de copie combinée à ce qui swapprécède.


1
Dans votre exemple, le swap est inutile, copy elision construirait de maptoute façon la valeur de retour . La technique que vous montrez est utile si elle mapexiste déjà, plutôt que d'être simplement construite. L'exemple serait meilleur sans le commentaire "cheap default constructor" et avec "// ..." entre cette construction et le swap
Jonathan Wakely

Je l'ai changé selon votre suggestion. Merci.
Andrzej

L'utilisation de «gros» et de «plus gros» prête à confusion. Pourquoi ne pas expliquer l'importance de la taille de la clé et du type de valeur?
einpoklum

1

Quand j'ai remarqué qu'un compilateur utilisant le standard C ++ 11 ne comporte plus de défauts avec le code suivant:

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

pour soi-disant contenant opérateur >>, j'ai commencé à danser. Dans les versions antérieures, il fallait faire

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

Pour aggraver les choses, si jamais vous deviez déboguer cela, vous savez à quel point les messages d'erreur qui en découlent sont horribles.

Cependant, je ne sais pas si cela vous était "évident".


1
Cette fonctionnalité a déjà été ajoutée dans le C ++ précédent. Ou du moins, Visual C ++ l'a implémenté conformément à la discussion sur les normes plusieurs années auparavant.
Alan Baljeu

1
@AlanBaljeu Bien sûr, il y a beaucoup de choses non standard ajoutées au compilateur / bibliothèques. Il y avait des tonnes de compilateurs qui avaient une déclaration de variable "auto" avant C ++ 11, mais vous ne pouviez pas être sûr que votre code puisse réellement être compilé par autre chose. La question portait sur le standard, pas sur "y avait-il un compilateur capable de faire cela".
v010dya

1

Le retour par valeur n'est plus un problème. Avec la sémantique de déplacement et / ou l'optimisation de la valeur de retour (dépendante du compilateur), les fonctions de codage sont plus naturelles sans frais généraux ni coût (la plupart du temps).


... mais quel idiome est obsolète?
einpoklum

Ce n'était pas un idiome mais c'était une bonne pratique qui n'était plus nécessaire. Même avec RVO pris en charge par le compilateur, ce qui est facultatif. en.wikipedia.org/wiki/Return_value_optimization "Dans les premiers stades de l'évolution du C ++, l'incapacité du langage à renvoyer efficacement un objet de type classe à partir d'une fonction était considérée comme une faiblesse ....." struct Data {char bytes [ 16]; }; void f (Data * p) {// génère le résultat directement dans * p} int main () {Data d; f (& d); }
Martin A

J'avais laissé entendre que vous devriez formuler votre réponse comme "la coutume d'éviter le retour par valeur n'est plus pertinente comme etc. etc. etc."
einpoklum
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.