Après avoir lu ce fameux discours de Linus Torvalds , je me suis demandé quels étaient en réalité tous les pièges pour les programmeurs en C ++. Je ne fais pas explicitement référence aux fautes de frappe ou au mauvais déroulement du programme tel qu'il est traité dans cette question et ses réponses , mais à un plus grand nombre d'erreurs de haut niveau qui ne sont pas détectées par le compilateur et qui ne génèrent pas de bugs évidents au premier lancement, mais d'erreurs de conception complètes, Ce qui est improbable en C mais qui est susceptible d’être fait en C ++ par les nouveaux arrivants qui ne comprennent pas toutes les implications de leur code.
Je me réjouis également des réponses soulignant une baisse considérable des performances, à laquelle on ne s'attend généralement pas. Voici un exemple de ce qu'un de mes professeurs m'a dit à propos d'un générateur d'analyseur syntaxique LR (1):
Vous avez utilisé un peu trop d'instances d'héritage et de virtualité inutiles. L'héritage rend une conception beaucoup plus compliquée (et inefficace en raison du sous-système RTTI (inférence de type au moment de l'exécution)) et ne doit donc être utilisée que là où cela est utile, par exemple pour les actions de la table d'analyse. Parce que vous utilisez beaucoup les modèles, vous n’avez pratiquement pas besoin d’héritage. "
virtual
fonctions, non?dynamic_cast
doit réussir ou non, et peu d'autres choses, mais la réflexion en couvre beaucoup plus, notamment le fait de pouvoir récupérer des informations sur les attributs ou les fonctions des membres, ce qui n'est pas le cas. présent en C ++.Réponses:
Torvalds parle de son cul ici.
OK, pourquoi il parle de son cul:
Tout d’abord, son discours n’est vraiment rien MAIS un discours. Il y a très peu de contenu réel ici. La seule raison pour laquelle il est vraiment célèbre ou même légèrement respecté est qu’il a été créé par Dieu Linux. Son principal argument est que le C ++ est une merde et qu'il aime faire chier les gens du C ++. Il n'y a bien sûr aucune raison de répondre à cela et quiconque considère cela comme un argument raisonnable est au-delà de la conversation.
En ce qui concerne ce qui pourrait être lu comme ses points les plus objectifs:
En gros, Torvalds parle de son cul. Il n'y a pas d'argument intelligible fait pour rien. S'attendre à une réfutation sérieuse de telles absurdités est tout simplement ridicule. On me dit de "développer" une réfutation de quelque chose que je serais censé développer si c'est là où je l'ai dit. Si vous regardez vraiment, honnêtement, ce que Torvalds a dit, vous verrez qu'il n'a rien dit.
Tout simplement parce que Dieu dit que cela ne signifie pas que cela a un sens ou doit être pris plus au sérieux que si un bozo aléatoire le disait. À vrai dire, Dieu n'est qu'un autre bozo aléatoire.
Répondre à la question réelle:
La pire et la plus courante des mauvaises pratiques C ++ est de la traiter comme un C. L’utilisation continue des fonctions de l’API C comme printf, gets (également considéré comme mauvais en C), strtok, etc. ne manque pas seulement de tirer parti de la puissance fournie par le système de types plus étroit, ils entraînent inévitablement de nouvelles complications lorsqu’on essaie d’interagir avec du "vrai" code C ++. Donc, fondamentalement, faites exactement le contraire de ce que Torvalds conseille.
Apprenez à tirer parti des technologies STL et Boost pour mieux détecter les bogues en temps voulu et pour vous simplifier la vie de manière générale (le tokenizer boost, par exemple, est à la fois dactylographié et de meilleure qualité). Il est vrai que vous devrez apprendre à lire les erreurs de modèle, ce qui est déconcertant au début, mais (d'après mon expérience, c'est beaucoup plus facile que d'essayer de déboguer quelque chose qui génère un comportement indéfini pendant l'exécution, ce que l'API C fait assez facile à faire.
Pour ne pas dire que C n'est pas aussi bon. Bien sûr, j'aime mieux le C ++. Les programmeurs C aiment mieux C Il y a des compromis et des goûts subjectifs en jeu. Il y a aussi beaucoup de désinformation et de FUD qui circulent. Je dirais qu'il y a plus de FUD et de désinformation autour du C ++, mais je suis partial à cet égard. Par exemple, les problèmes de "gonflement" et de "performance" supposés du C ++ ne sont en réalité pas des problèmes majeurs la plupart du temps et sont certainement dépassés par les proportions de la réalité.
En ce qui concerne les problèmes auxquels votre professeur fait référence, ceux-ci ne sont pas propres au C ++. En programmation orientée objet (et en programmation générique), vous souhaitez préférer la composition à l'héritage. L'héritage est la relation de couplage la plus forte possible existant dans tous les langages OO. C ++ en ajoute un qui est plus fort, l'amitié. L'héritage polymorphe devrait être utilisé pour représenter des abstractions et les relations "est-un", il ne devrait jamais être utilisé pour une réutilisation. Il s'agit de la deuxième erreur la plus importante que vous puissiez commettre en C ++. C'est une erreur assez importante, mais elle est loin d'être unique en son genre. Vous pouvez également créer des relations d'héritage trop complexes en C # ou en Java, et ils auront exactement les mêmes problèmes.
la source
J'ai toujours pensé que les dangers du C ++ étaient fortement exagérés par des programmeurs inexpérimentés, C avec Classes.
Oui, le C ++ est plus difficile à maîtriser que quelque chose comme Java, mais si vous programmez à l'aide de techniques modernes, il est assez facile d'écrire des programmes robustes. Je n'ai pas honnêtement que beaucoup plus difficile d'une programmation de temps en C ++ que je fais dans des langages comme Java, et je me retrouve souvent l' absence de certaines abstractions C ++ comme modèles et RAII quand je conception dans d' autres langues.
Cela dit, même après des années de programmation en C ++, de temps en temps, je commettrai une erreur vraiment stupide qui ne serait pas possible dans un langage de niveau supérieur. Un défaut courant en C ++ est d’ignorer la durée de vie des objets: en Java et C #, vous n’avez généralement pas à vous soucier de la durée de vie des objets *, car tous les objets existent sur le tas et sont gérés pour vous par un récupérateur de déchets magique.
Maintenant, en C ++ moderne, vous n'avez généralement pas à vous soucier de la durée de vie d'un objet. Vous avez des destructeurs et des pointeurs intelligents qui gèrent pour vous la durée de vie des objets. 99% du temps, cela fonctionne à merveille. Mais de temps en temps, vous vous ferez toucher par un pointeur (ou une référence) suspendu. Par exemple, tout récemment, j'avais un objet (appelons-le
Foo
) qui contenait une variable de référence interne à un autre objet (appelons-leBar
). À un moment donné, j’ai arrangé bêtement les choses de manière à ce que celaBar
devienne hors de portée auparavantFoo
,Foo
le destructeur de yet a fini par appeler une fonction membre deBar
. Inutile de dire que les choses ne se sont pas bien passées.Maintenant, je ne peux pas vraiment blâmer le C ++ pour cela. C'était ma propre mauvaise conception, mais le fait est que ce genre de chose ne se produirait pas dans un langage géré de niveau supérieur. Même avec des pointeurs intelligents et similaires, il est parfois nécessaire de rester conscient de la durée de vie d'un objet.
* Si la ressource gérée est de la mémoire, alors.
la source
La différence de code est généralement plus liée au programmeur qu'au langage. En particulier, un bon programmeur C ++ et un programmeur C trouveront des solutions similaires (même différentes). Maintenant, C est un langage plus simple (en tant que langage) et cela signifie qu'il y a moins d'abstractions et plus de visibilité sur ce que le code fait réellement.
Une partie de son discours (il est connu pour ses discours contre le C ++) est basé sur le fait que davantage de personnes s’intéressent au C ++ et écrivent du code sans vraiment comprendre ce que certaines abstractions cachent et faire de fausses suppositions.
la source
std::vector<bool>
modification de chaque valeur?for ( std::vector<bool>::iterator it = v.begin(), end = v.end(); it != end; ++it ) { *it = !*it; }
? Qu'est-ce qui est abstrait*it = !*it;
?std::vector<bool>
c'est une erreur bien connue, mais c'est un très bon exemple de ce qui est discuté: les abstractions sont bonnes, mais vous devez faire attention à ce qu'elles cachent. La même chose peut et va se passer dans le code utilisateur. Pour commencer, j’ai vu des utilisateurs de C ++ et de Java utiliser des exceptions pour effectuer un contrôle de flux et un code qui ressemble à un appel de fonction imbriquée qui est en fait un lanceur d’exception de sauvetage:void endOperation();
implémenté sousthrow EndOperation;
. Un bon programmeur évitera ces constructions surprenantes , mais le fait est que vous pouvez les trouver.Surutilisation de
try/catch
blocs.Cela provient généralement de langages tels que Java et les gens diront que C ++ manque de
finalize
clause.Mais ce code présente deux problèmes:
file
avanttry/catch
, car vous ne pouvez pas créer declose
fichier qui n’existe pas danscatch
. Cela conduit à une "fuite de champ" dans ce quifile
est visible après avoir été fermé. Vous pouvez ajouter un bloc mais ...: /return
au milieu de latry
portée, le fichier n'est pas fermé (c'est pourquoi les gens reprochent à l'absence definalize
clause)Cependant, en C ++, nous avons des moyens beaucoup plus efficaces de traiter ce problème que:
finalize
using
defer
Nous avons RAII, dont la propriété vraiment intéressante est le mieux résumée
SBRM
(gestion des ressources limitées).En fabriquant la classe de sorte que son destructeur nettoie les ressources dont il est propriétaire, nous ne confions pas la responsabilité de la gestion de la ressource à chacun de ses utilisateurs!
C'est la fonctionnalité qui me manque dans n'importe quelle autre langue, et probablement celle qui est la plus oubliée.
La vérité est qu’il est rarement nécessaire d’écrire un
try/catch
bloc en C ++, si ce n’est au plus haut niveau, afin d’éviter la résiliation sans journalisation.la source
fopen
etfclose
ici.) RAII est la façon « correcte » de faire les choses, mais il est peu pratique pour les gens qui veulent utiliser les bibliothèques C de C ++ .File file("some.txt");
et c'est tout (nonopen
, nonclose
, nontry
...)Une erreur courante qui correspond à vos critères est de ne pas comprendre le fonctionnement des constructeurs de copie s’agissant de la mémoire allouée dans votre classe. J'ai perdu le compte du temps que j'ai passé à réparer les crashs ou les fuites de mémoire parce qu'un "noob" a placé ses objets dans une carte ou un vecteur et n'a pas écrit correctement les constructeurs de copie et les destructeurs.
Malheureusement, le C ++ est plein de pièges "cachés" comme celui-ci. Mais se plaindre, c'est comme se plaindre de se rendre en France et de ne pas comprendre ce que les gens disaient. Si vous allez y aller, apprenez la langue.
la source
C ++ permet une grande variété de fonctionnalités et de styles de programmation, mais cela ne signifie pas pour autant que ce sont de bonnes manières d'utiliser le C ++. Et en fait, il est incroyablement facile d'utiliser C ++ de manière incorrecte.
Il doit être appris et compris correctement , le simple fait d'apprendre en pratiquant (ou en l'utilisant comme si on utilisait un autre langage) conduirait à un code inefficace et source d'erreurs.
la source
Eh bien ... Pour commencer, vous pouvez lire la FAQ Lite de C ++
Ensuite, plusieurs personnes ont construit des carrières en écrivant des livres sur les subtilités du C ++:
Herb Sutter et Scott Meyers, à savoir.
En ce qui concerne le discours de Torvalds qui manque de substance ... allez, sérieusement: personne n’a eu autant d’encre à parler de ses nuances. Vos livres Python & Ruby & Java se concentrent tous sur l’écriture d’applications ... vos livres C ++ traitent de fonctionnalités / conseils / pièges en langage idiot.
la source
Des modèles trop lourds peuvent ne pas entraîner de bogues au début. Avec le temps, les utilisateurs devront modifier ce code et auront du mal à comprendre un énorme modèle. C'est alors que les bogues entrent - les incompréhensions provoquent des commentaires "Il compile et exécute", ce qui conduit souvent à un code presque correct mais pas tout à fait correct.
Généralement, si je me vois faire un modèle générique profond à trois niveaux, je m'arrête et pense à la façon dont il pourrait être réduit à un. Souvent, le problème est résolu en extrayant des fonctions ou des classes.
la source
Attention: ce n'est pas aussi une réponse qu'une critique de la conversation à laquelle "utilisateur inconnu" était associé dans sa réponse.
Son premier point principal est le (soi-disant) "standard en constante évolution". En réalité, les exemples qu'il cite tous concernent des modifications de C ++ avant la standardisation. Depuis 1998 (date à laquelle la première norme C ++ a été finalisée), les modifications apportées au langage ont été plutôt minimes. En fait, nombreux sont ceux qui affirment que le vrai problème est que davantage de modifications auraient dû être apportées. Je suis raisonnablement certain que tout le code conforme à la norme C ++ d'origine reste conforme à la norme actuelle. Bien que ce soit un peu moins certain, à moins que quelque chose ne change rapidement (et de façon tout à fait inattendue), il en ira de même pour le prochain standard C ++ (en théorie, tout le code utilisé
export
va casser, mais pratiquement aucun n'existe; d’un point de vue pratique, ce n’est pas un problème). Je peux penser à quelques autres langages, systèmes d’exploitation (ou beaucoup d’autres éléments liés à l’informatique) pouvant prétendre à une telle affirmation.Il se lance ensuite dans des "styles en constante évolution". Encore une fois, la plupart de ses points sont assez proches du non-sens. Il essaie de caractériser
for (int i=0; i<n;i++)
comme "vieux et éclaté" etfor (int i(0); i!=n;++i)
"nouvelle chaleur". La réalité est que, même s’il existe certains types pour lesquels de tels changements pourraient avoir un sens,int
cela ne fait aucune différence - et même lorsque vous pouvez gagner quelque chose, il est rarement nécessaire de rédiger du code correct ou correct. Même au mieux, il fabrique une montagne à partir d'une taupinière.Son affirmation suivante est que C ++ "optimise dans la mauvaise direction" - en particulier, alors qu'il admet que l'utilisation de bonnes bibliothèques est facile, il affirme que C ++ "rend l'écriture de bonnes bibliothèques presque impossible." Je pense que c’est l’une de ses erreurs les plus fondamentales. En réalité, écrire de bonnes bibliothèques pour presque toutes les langues est extrêmement difficile. Au minimum, écrire une bonne bibliothèque nécessite de bien comprendre un domaine problématique afin que votre code fonctionne pour une multitude d'applications possibles dans ce domaine (ou en relation avec celui-ci). La plupart de ce que C ++ fait réellement est de "relever le niveau" - après avoir constaté à quel point une bibliothèque peut être meilleure , les gens sont rarement disposés à revenir à écrire le genre de foutaises qu'ils auraient autrement.de très bons codeurs écrivent pas mal de bibliothèques, qui peuvent ensuite être utilisées (facilement, comme il le reconnaît) par "le reste d'entre nous". C'est vraiment un cas où "ce n'est pas un bug, c'est une fonctionnalité".
Je ne vais pas essayer de toucher chaque point dans l'ordre (cela prendrait des pages), mais aller directement à son point de clôture. Il cite Bjarne: "L'optimisation de tout un programme peut être utilisée pour éliminer les tables de fonctions virtuelles et les données RTTI inutilisées. Une telle analyse convient particulièrement aux programmes relativement petits qui n'utilisent pas de liaison dynamique."
Il critique cela en affirmant sans fondement que "c'est un problème vraiment difficile", allant même jusqu'à le comparer au problème de fond. En réalité, il n'en est rien - c'est l'éditeur de liens inclus dans Zortech C ++ (le premier compilateur C ++ pour MS-DOS dans les années 1980) qui l'a fait. Il est vrai qu’il est difficile d’être certain que toutes les données potentiellement superflues ont été éliminées, mais qu’il est tout à fait raisonnable de faire un travail plutôt équitable.
Indépendamment de cela, toutefois, le point le plus important est que cela n’est absolument pas pertinent pour la plupart des programmeurs. Comme le savent ceux d’entre nous qui ont désassemblé pas mal de code, à moins que vous n’écriviez un langage assembleur sans aucune bibliothèque, vos exécutables contiennent presque certainement une bonne quantité de «choses» (code et données, dans des cas typiques) que probablement pas même savoir, pour ne jamais mentionner réellement utiliser. Pour la plupart des gens, la plupart du temps, cela n'a pas d'importance - à moins que vous ne développiez des systèmes embarqués les plus minuscules, cette consommation de stockage supplémentaire n'a tout simplement pas d'importance.
En fin de compte, il est vrai que ce discours a un peu plus de substance que l'idiotie de Linus - mais cela lui donne exactement le poids de la louange qu'il mérite.
la source
En tant que programmeur C qui a dû coder en C ++ à cause de circonstances inévitables, voici mon expérience. C’est très peu de choses que j’utilise, c’est le C ++ et la plupart du temps, c’est parce que je ne comprends pas très bien le C ++. Je n'avais / n'ai pas de mentor pour me montrer les subtilités du C ++ et comment écrire du bon code. Et sans l'aide d'un très bon code C ++, il est extrêmement difficile d'écrire un bon code en C ++. IMHO c'est le plus gros inconvénient de C ++ car il est difficile de trouver de bons codeurs C ++ prêts à prendre en main les débutants.
Certains des succès que j'ai vus sont généralement dus à l'allocation de mémoire magique de STL (oui, vous pouvez changer l'allocateur, mais qui le fait quand il commence avec C ++?). Vous entendez généralement les arguments des experts en C ++ selon lesquels les vecteurs et les tableaux offrent des performances similaires, car les vecteurs utilisent les tableaux en interne et que l'abstraction est extrêmement efficace. J'ai trouvé que cela était vrai dans la pratique pour l'accès vectoriel et la modification de valeurs existantes. Mais ce n'est pas vrai pour l'ajout d'une nouvelle entrée, la construction et la destruction de vecteurs. gprof a montré que, cumulativement, 25% du temps d'une application était consacré aux constructeurs de vecteurs, aux destructeurs, à memmove (déplacement d'un vecteur entier pour l'ajout d'un nouvel élément) et à d'autres opérateurs vectoriels surchargés (comme ++).
Dans la même application, le vecteur de quelque choseSmall a été utilisé pour représenter un quelque chose de grand. Il n'y avait pas besoin d'un accès aléatoire à quelque choseSmall dans quelque chose de Big. Encore un vecteur a été utilisé à la place d'une liste. La raison pour laquelle le vecteur a été utilisé? Parce que le codeur d'origine était familier avec la syntaxe de type tableau de vecteurs et pas très familier avec les itérateurs nécessaires aux listes (oui, il est issu d'un fond C). Cela prouve que beaucoup d’experts sont nécessaires pour maîtriser le C ++. C offre si peu de constructions de base, sans aucune abstraction, que vous pouvez l’obtenir plus facilement que C ++.
la source
Bien que j'aime bien Linus Thorvalds, ce coup de gueule est sans substance - juste un coup de gueule.
Si vous souhaitez assister à une véritable coup de gueule, en voici une: "Pourquoi le C ++ nuit-il à l'environnement, provoque le réchauffement planétaire et tue les chiots" http://chaosradio.ccc.de/camp2007_m4v_1951.html Matériel supplémentaire: http: // www .fefe.de / c ++ /
Un discours divertissant, à mon humble avis
la source
STL et boost sont portables, au niveau du code source. Je suppose que ce dont parle Linus, c’est que C ++ n’a pas d’ABI (interface binaire d’application). Vous devez donc compiler toutes les bibliothèques avec lesquelles vous êtes liés, avec la même version du compilateur et avec les mêmes commutateurs, sinon vous limiter à l’ABI C au niveau des bibliothèques de la DLL. Je trouve également que annyoing .. mais à moins de créer des bibliothèques tierces, vous devriez être capable de prendre le contrôle de votre environnement de construction. Je trouve que me limiter au C ABI ne vaut pas la peine. La commodité de pouvoir passer des chaînes, des vecteurs et des pointeurs intelligents d’une dll à une autre vaut la peine de reconstruire toutes les bibliothèques lors de la mise à niveau des compilateurs ou de la modification de leurs commutateurs. Les règles d'or que je suis sont les suivantes:
-Hériter de réutiliser l'interface, pas la mise en œuvre
-Préférencer l'agrégation sur l'héritage
-Préférer si possible des fonctions libres aux méthodes membres
-Toujours utiliser l'idiome RAII pour sécuriser fortement votre code. Évitez les prises.
-Utilisez les pointeurs intelligents, évitez les pointeurs nus (non possédés)
-Préférer la sémantique de valeur à la sémantique de référence
-Ne réinventez pas la roue, utilisez stl et boost
-Utilisez l'idiome Pimpl pour masquer les données privées et / ou pour fournir un pare-feu de compilation
la source
Ne pas mettre une finale
;
à la fin d'une déclaration de clase, du moins dans certaines versions de VC.la source