Joel Spolsky a qualifié le C ++ de "suffisamment de corde pour se suspendre" . En fait, il résumait "Effective C ++" par Scott Meyers:
C'est un livre qui dit essentiellement que le C ++ est assez de corde pour vous pendre, puis quelques kilomètres supplémentaires de corde, puis quelques pilules suicide qui sont déguisées en M&M ...
Je n'ai pas de copie du livre, mais il y a des indications qu'une grande partie du livre se rapporte aux pièges de la gestion de la mémoire qui semblent être rendus sans objet en C # car le runtime gère ces problèmes pour vous.
Voici mes questions:
- C # évite-t-il les pièges qui sont évités en C ++ uniquement par une programmation minutieuse? Si oui, dans quelle mesure et comment sont-ils évités?
- Y a-t-il de nouveaux pièges différents en C # dont un nouveau programmeur C # devrait être conscient? Si c'est le cas, pourquoi ne pourraient-ils pas être évités par la conception de C #?
c#
programming-languages
c++
alx9r
la source
la source
Your questions should be reasonably scoped. If you can imagine an entire book that answers your question, you’re asking too much.
. Je crois que cela se qualifie comme une telle question ...Réponses:
La différence fondamentale entre C ++ et C # provient d' un comportement indéfini .
Cela n'a rien à voir avec la gestion manuelle de la mémoire. Dans les deux cas, c'est un problème résolu.
C / C ++:
En C ++, lorsque vous faites une erreur, le résultat n'est pas défini.
Ou, si vous essayez de faire certains types d'hypothèses sur le système (par exemple débordement d'entier signé), il est probable que votre programme ne soit pas défini.
Peut-être lire cette série en 3 parties sur le comportement indéfini.
C'est ce qui rend C ++ si rapide - le compilateur n'a pas à se soucier de ce qui se passe lorsque les choses tournent mal, il peut donc éviter de vérifier l'exactitude.
C #, Java, etc.
En C #, vous avez la garantie que de nombreuses erreurs vous exploseront au visage en tant qu'exceptions, et vous en aurez bien plus sur le système sous-jacent.
C'est une barrière fondamentale pour rendre C # aussi rapide que C ++, mais c'est aussi une barrière fondamentale pour rendre C ++ sûr, et cela rend C # plus facile à utiliser et à déboguer.
Tout le reste n'est que de la sauce.
la source
La plupart le font, certains non. Et bien sûr, il en fait de nouveaux.
Comportement indéfini - Le plus grand écueil avec C ++ est qu'il y a beaucoup de langage non défini. Le compilateur peut littéralement faire exploser l'univers lorsque vous faites ces choses, et ça ira. Naturellement, cela n'est pas courant , mais il est assez courant que votre programme fonctionne correctement sur une machine et pour de bonnes raisons ne fonctionne pas sur une autre. Ou pire, agissez subtilement différemment. C # a quelques cas de comportement indéfini dans ses spécifications, mais ils sont rares et dans des zones du langage qui sont peu fréquentées. C ++ a la possibilité d'exécuter un comportement indéfini chaque fois que vous faites une déclaration.
Fuites de mémoire - C'est moins une préoccupation pour le C ++ moderne, mais pour les débutants et pendant environ la moitié de sa durée de vie, C ++ a rendu la fuite de mémoire super facile. Un C ++ efficace est venu autour de l'évolution des pratiques pour éliminer cette préoccupation. Cela dit, C # peut toujours fuir la mémoire. Le cas le plus courant rencontré par les utilisateurs est la capture d'événements. Si vous avez un objet et mettez l'une de ses méthodes en tant que gestionnaire d'un événement, le propriétaire de cet événement doit être GC'd pour que l'objet meure. La plupart des débutants ne réalisent pas que le gestionnaire d'événements compte comme référence. Il y a aussi des problèmes à ne pas disposer de ressources jetables qui peuvent fuir la mémoire, mais ce ne sont pas aussi communs que les pointeurs dans C ++ pré-efficace.
Compilation - C ++ a un modèle de compilation retardée. Cela conduit à un certain nombre de trucs pour bien jouer avec et réduire les temps de compilation.
Chaînes - Le C ++ moderne améliore un peu les choses, mais
char*
est responsable d'environ 95% de toutes les violations de sécurité avant l'an 2000. Pour les programmeurs expérimentés, ils se concentreront surstd::string
, mais c'est toujours quelque chose à éviter et un problème dans les bibliothèques anciennes / pires . Et c'est prier pour que vous n'ayez pas besoin du support Unicode.Et vraiment, c'est la pointe de l'iceberg. Le principal problème est que C ++ est un langage très pauvre pour les débutants. C'est assez incohérent, et beaucoup d'anciens vraiment, pièges vraiment mauvais ont été résolus en changeant les idiomes. Le problème est que les débutants doivent ensuite apprendre les idiomes de quelque chose comme Effective C ++. C # élimine un grand nombre de ces problèmes et rend le reste moins préoccupant jusqu'à ce que vous progressiez dans le parcours d'apprentissage.
J'ai mentionné le problème d'événement "fuite de mémoire". Ce n'est pas un problème de langage tant que le programmeur attend quelque chose que le langage ne peut pas faire.
Un autre est que le finaliseur d'un objet C # n'est pas techniquement garanti pour être exécuté par le runtime. Ce n'est généralement pas importance, mais cela fait que certaines choses sont conçues différemment que vous ne le pensez.
Un autre demi-piège dans lequel j'ai vu des programmeurs est la sémantique de capture des fonctions anonymes. Lorsque vous capturez une variable, vous capturez la variable . Exemple:
Ne fait pas ce que l'on pense naïvement. Cela imprime
10
10 fois.Je suis sûr qu'il y a un certain nombre d'autres que j'oublie, mais le principal problème est qu'ils sont moins omniprésents.
la source
char*
. Sans oublier que vous pouvez toujours fuir la mémoire en C # très bien.enable_if
À mon avis, les dangers du C ++ sont quelque peu exagérés.
Le danger essentiel est le suivant: tandis que C # vous permet d'effectuer des opérations de pointeur «non sécurisées» à l'aide de la commande
unsafe
mot clé, C ++ (étant principalement un surensemble de C) vous permettra d'utiliser des pointeurs chaque fois que vous en aurez envie. Outre les dangers habituels inhérents à l'utilisation de pointeurs (qui sont les mêmes avec C), comme les fuites de mémoire, les débordements de tampon, les pointeurs pendants, etc., C ++ introduit de nouvelles façons pour vous de sérieusement bousiller les choses.Cette "corde supplémentaire", pour ainsi dire, dont parlait Joel Spolsky , se résume essentiellement à une chose: écrire des cours qui gèrent en interne leur propre mémoire, également connue sous le nom de " Règle de 3 " (qui peut maintenant être appelée la Règle de 4 ou règle de 5 en C ++ 11). Cela signifie que si vous souhaitez écrire une classe qui gère ses propres allocations de mémoire en interne, vous devez savoir ce que vous faites, sinon votre programme se bloquera probablement. Vous devez soigneusement créer un constructeur, un constructeur de copie, un destructeur et un opérateur d'affectation, ce qui est étonnamment facile à se tromper, ce qui entraîne souvent des plantages bizarres lors de l'exécution.
TOUTEFOIS , dans la programmation C ++ quotidienne réelle, il est très rare d'écrire une classe qui gère sa propre mémoire, il est donc trompeur de dire que les programmeurs C ++ doivent toujours être "prudents" pour éviter ces pièges. Habituellement, vous ferez simplement quelque chose de plus comme:
Cette classe ressemble assez à ce que vous feriez en Java ou en C # - elle ne nécessite aucune gestion de mémoire explicite (car la classe de bibliothèque
std::string
s'occupe de tout cela automatiquement), et aucune substance "Rule of 3" n'est requise du tout depuis la valeur par défaut constructeur de copie et opérateur d'affectation est très bien.Ce n'est que lorsque vous essayez de faire quelque chose comme:
Dans ce cas, il peut être difficile pour les novices de corriger l'affectation, le destructeur et le constructeur de copie. Mais pour la plupart des cas, il n'y a aucune raison de le faire. C ++ permet d'éviter très facilement la gestion manuelle de la mémoire 99% du temps en utilisant des classes de bibliothèque comme
std::string
etstd::vector
.Un autre problème connexe est la gestion manuelle de la mémoire d'une manière qui ne prend pas en compte la possibilité d'une exception levée. Comme:
Si en
some_function_which_may_throw()
fait ne jette une exception, vous vous retrouvez avec une fuite de mémoire car la mémoire allouées
ne sera jamais récupéré. Mais encore une fois, dans la pratique, ce n'est plus un problème pour la même raison que la «règle de 3» n'est plus vraiment un problème. Il est très rare (et généralement inutile) de gérer réellement votre propre mémoire avec des pointeurs bruts. Pour éviter le problème ci-dessus, tout ce que vous devez faire est d'utiliser unstd::string
oustd::vector
, et le destructeur sera automatiquement invoqué lors du déroulement de la pile après la levée de l'exception.Ainsi, un thème général ici est que de nombreuses fonctionnalités C ++ qui n'ont pas été héritées de C, telles que l'initialisation / destruction automatique, les constructeurs de copie et les exceptions, obligent un programmeur à être extrêmement prudent lors de la gestion manuelle de la mémoire en C ++. Mais encore une fois, ce n'est un problème que si vous avez l'intention de faire une gestion manuelle de la mémoire en premier lieu, ce qui n'est presque plus nécessaire lorsque vous avez des conteneurs standard et des pointeurs intelligents.
Donc, à mon avis, alors que le C ++ vous donne beaucoup de corde supplémentaire, il n'est presque jamais nécessaire de l'utiliser pour vous accrocher, et les pièges dont Joel parlait sont trivialement faciles à éviter dans le C ++ moderne.
la source
Does C# avoid pitfalls that are avoided in C++ only by careful programming?
. La réponse est "pas vraiment, car il est trivialement facile d'éviter les pièges dont Joel parlait en C ++ moderne"Je ne serais pas vraiment d'accord. Peut-être moins de pièges que le C ++ tel qu'il existait en 1985.
Pas vraiment. Des règles comme la règle des trois ont perdu une importance considérable en C ++ 11 grâce à
unique_ptr
etshared_ptr
étant normalisées. Utiliser les classes Standard d'une manière vaguement sensée n'est pas du "codage soigneux", c'est du "codage de base". De plus, la proportion de la population C ++ qui est encore suffisamment stupide, mal informée ou les deux pour faire des choses comme la gestion manuelle de la mémoire est beaucoup plus faible qu'auparavant. La réalité est que les enseignants qui souhaitent démontrer de telles règles doivent passer des semaines à essayer de trouver des exemples où elles s'appliquent toujours, car les classes standard couvrent pratiquement tous les cas d'utilisation imaginables. De nombreuses techniques efficaces C ++ ont suivi le même chemin - le chemin du dodo. Beaucoup d'autres ne sont pas vraiment spécifiques à C ++. Laisse moi voir. En sautant le premier élément, les dix suivants sont les suivants:final
etoverride
ont aidé à changer ce jeu en particulier pour le mieux. Faites votre destructeuroverride
et vous garantissez une belle erreur de compilation si vous héritez de quelqu'un qui n'a pas fait son destructeurvirtual
. Faites votre classefinal
et aucun mauvais gommage ne peut venir et en hériter accidentellement sans destructeur virtuel.Évidemment, je ne vais pas passer en revue tous les éléments C ++ effectifs, mais la plupart d'entre eux appliquent simplement des concepts de base au C ++. Vous trouverez les mêmes conseils dans n'importe quel langage d'opérateur surchargeable orienté objet de type valeur. Les destructeurs virtuels sont à peu près le seul à être un piège C ++ et sont toujours valides - bien que, sans doute, avec la
final
classe C ++ 11, il ne soit pas aussi valide qu'il l'était. Rappelez-vous qu'effective C ++ a été écrit lorsque l'idée d'appliquer la POO, et les fonctionnalités spécifiques de C ++, était encore très nouvelle. Ces éléments ne concernent guère les pièges de C ++ et plus sur la façon de gérer le changement de C et d'utiliser correctement la POO.Edit: Les pièges de C ++ n'incluent pas des choses comme les pièges de
malloc
. Je veux dire, d'une part, chaque écueil que vous pouvez trouver dans le code C, vous pouvez également trouver dans le code C # dangereux, donc ce n'est pas particulièrement pertinent, et deuxièmement, ce n'est pas parce que la norme le définit pour l'interopération que l'utilisation est considérée comme C ++ code. La norme définitgoto
également, mais si vous deviez écrire un tas géant de gâchis de spaghetti en l'utilisant, je considérerais que c'est votre problème, pas celui de la langue. Il y a une grande différence entre «codage soigneux» et «suivre les idiomes de base du langage».using
suce. C'est vraiment le cas. Et je n'ai aucune idée pourquoi quelque chose de mieux n'a pas été fait. De plus,Base[] = Derived[]
et à peu près toutes les utilisations d'Object, qui existent parce que les concepteurs originaux n'ont pas remarqué le succès massif que les modèles étaient en C ++, et ont décidé que "Faisons tout hériter de tout et perdons toute notre sécurité de type" était le choix le plus intelligent . Je crois aussi que vous pouvez trouver de mauvaises surprises dans des choses comme les conditions de course avec les délégués et d'autres amusements de ce genre. Ensuite, il y a d'autres choses générales, comme la façon dont les génériques sont horriblement mauvais par rapport aux modèles, le placement forcé vraiment vraiment inutile de tout dans unclass
, et de telles choses.la source
malloc
ne signifie pas que vous devez le faire, pas plus que le simple fait que vous puissiez vous prostituergoto
comme une chienne signifie que c'est une corde avec laquelle vous pouvez vous accrocher.unsafe
en C #, ce qui est tout aussi mauvais. Je pourrais également énumérer tous les écueils du codage C # comme C, si vous le souhaitez.C # a les avantages de:
char
,string
etc. est définie par l' implémentation. Le schisme entre l'approche Windows d'Unicode (wchar_t
pour UTF-16,char
pour les "pages de code" obsolètes) et l'approche * nix (UTF-8) provoque de grandes difficultés dans le code multiplateforme. C #, OTOH, garantit que astring
est UTF-16.Oui:
IDisposable
Il y a un livre appelé Effective C # qui a une structure similaire à Effective C ++ .
la source
Non, C # (et Java) sont moins sûrs que C ++
C ++ est vérifiable localement . Je peux inspecter une seule classe en C ++ et déterminer que la classe ne perd pas de mémoire ou d'autres ressources, en supposant que toutes les classes référencées sont correctes. En Java ou C #, il est nécessaire de vérifier chaque classe référencée pour déterminer si elle nécessite une finalisation quelconque.
C ++:
C #:
C ++:
C #:
la source
auto_ptr
(ou quelques-uns de ses proches). Telle est la corde proverbiale.auto_ptr
est aussi simple que savoir utiliserIEnumerable
ou savoir utiliser des interfaces, ou ne pas utiliser de virgule flottante pour une devise ou autre. C'est une application de base de DRY. Personne qui connaît les bases de la programmation ne ferait cette erreur. Contrairementusing
. Le problèmeusing
est que vous devez savoir pour chaque classe s'il est jetable (et j'espère que cela ne changera jamais), et si ce n'est pas jetable, vous interdisez automatiquement toutes les classes dérivées qui pourraient devoir être jetables.Dispose
méthode, vous devez l'implémenterIDisposable
(de la manière «appropriée»). Si votre classe fait cela (ce qui équivaut à implémenter RAII pour votre classe en C ++), et que vous utilisezusing
(ce qui est comme les pointeurs intelligents en C ++), tout fonctionne parfaitement. Le finaliseur est principalement destiné à prévenir les accidents -Dispose
est responsable de l'exactitude, et si vous ne l'utilisez pas, eh bien, c'est votre faute, pas C #.Oui 100% oui car je pense qu'il est impossible de libérer de la mémoire et de l'utiliser en C # (en supposant qu'il soit géré et que vous ne passiez pas en mode dangereux).
Mais si vous savez programmer en C ++, ce qui n'est pas le cas d'un nombre incroyable de personnes. Tu vas bien. Comme les classes Charles Salvia ne gèrent pas vraiment leurs souvenirs car tout est géré dans les classes STL préexistantes. J'utilise rarement des pointeurs. En fait, je suis allé des projets sans utiliser un seul pointeur. (C ++ 11 facilite cela).
En ce qui concerne les fautes de frappe, les erreurs idiotes, etc. (ex:
if (i=0)
bc la clé s'est bloquée lorsque vous appuyez sur == très rapidement), le compilateur se plaint, ce qui est agréable car il améliore la qualité du code. D'autres exemples oublient lesbreak
instructions switch et ne vous permettent pas de déclarer des variables statiques dans une fonction (ce que je n'aime pas parfois mais c'est une bonne idée imo).la source
=
/==
en utilisant==
pour l'égalité de référence et en introduisant.equals
pour l'égalité de valeur. Le pauvre programmeur doit maintenant savoir si une variable est «double» ou «double» et être sûr d'appeler la bonne variante.struct
vous pouvez faire==
ce qui fonctionne incroyablement bien car la plupart du temps, il n'y a que des chaînes, des entiers et des flottants (c'est-à-dire uniquement des membres struct). Dans mon propre code, je n'ai jamais ce problème, sauf lorsque je veux comparer des tableaux. Je ne pense pas avoir jamais comparé des types de liste ou non structurés (chaîne, int, float, DateTime, KeyValuePair et bien d'autres)==
pour l'égalité des valeurs etis
pour l'égalité des références.