Aspects problématiques de la programmation en C ++


En C ++, il existe de nombreuses fonctionnalitĂ©s qui peuvent ĂȘtre considĂ©rĂ©es comme potentiellement dangereuses - avec des erreurs de calcul dans la conception ou un codage inexact, elles peuvent facilement conduire Ă  des erreurs. L'article fournit une sĂ©lection de ces fonctionnalitĂ©s, donne des conseils sur la façon de rĂ©duire leur impact nĂ©gatif.




Table des matiĂšres


Table des matiĂšres

Présentation
1. Types
1.1. Instructions et opérateurs conditionnels
1.2. Conversions implicites
2. Résolution du nom
2.1. Masquage de variables dans des étendues imbriquées
2.2. Surcharge de fonction
3. Constructeurs, destructeurs, initialisation, suppression
3.1. Fonctions membres de classe générées par le compilateur
3.2. Variables non initialisées
3.3. Procédure d'initialisation pour les classes de base et les membres de classe non statiques
3.4. Procédure d'initialisation pour les membres de classe statique et les variables globales
3.5. Exceptions dans les destructeurs
3.6. Suppression d'objets et de tableaux dynamiques
3.7. Suppression lorsque la déclaration de classe est incomplÚte
4. Opérateurs, expressions
4.1. Priorité de l'opérateur
4.2. Surcharge de l'opérateur
4.3. La procédure de calcul des sous-expressions
5. Fonctions virtuelles
5.1 Remplacement des fonctions virtuelles
5.2 Surcharge et utilisation des paramÚtres par défaut
5.3 Appel de fonctions virtuelles dans le constructeur et le destructeur
5.4 Destructeur virtuel
6. Travail direct avec mémoire
6.1 Débordement du tampon
6.2 Chaßnes terminées par Z
6.3 Fonctions Ă  nombre de paramĂštres variable
7. Syntaxe
7.1 Annonces compliquées
7.2 Ambiguïté de syntaxe
8. Divers
8.1 Mot-clé en ligne et ODR
8.2 Fichiers d'en-tĂȘte
8.3 instruction switch
8.4 Passage des paramĂštres par valeur
8.5 Gestion des ressources
8.6 Liens propriétaires et non propriétaires
8.7 Compatibilité binaire
8.8 Macros
9. Résumé
Les références



Praemonitus, praemunitus.
Averti signifie armé. (lat.)



Présentation


En C ++, il existe de nombreuses fonctionnalitĂ©s qui peuvent ĂȘtre considĂ©rĂ©es comme potentiellement dangereuses - avec des erreurs de calcul dans la conception ou un codage inexact, elles peuvent facilement conduire Ă  des erreurs. Certains d'entre eux peuvent ĂȘtre attribuĂ©s Ă  une enfance difficile, certains Ă  la norme C ++ 98 obsolĂšte, mais d'autres sont dĂ©jĂ  associĂ©s aux fonctionnalitĂ©s du C ++ moderne. ConsidĂ©rez les principaux et essayez de donner des conseils sur la façon de rĂ©duire leur impact nĂ©gatif.



1. Types



1.1. Instructions et opérateurs conditionnels


Le besoin de compatibilité avec C conduit au fait que dans l'instruction if(...) et similaires, vous pouvez remplacer n'importe quelle expression numérique ou pointeur, et pas seulement des expressions comme bool . Le problÚme est aggravé par la conversion implicite de bool en int dans les expressions arithmétiques et la priorité de certains opérateurs. Cela conduit, par exemple, aux erreurs suivantes:


if(a=b) quand correctement if(a==b) ,
if(a<x<b) , quand correctement if(a<x && x<b) ,
if(a&x==0) , quand correctement if((a&x)==0) ,
if(Foo) quand correctement if(Foo()) ,
if(arr) quand correctement if(arr[0]) ,
if(strcmp(s,r)) lorsqu'il est correct if(strcmp(s,r)==0) .


Certaines de ces erreurs provoquent un avertissement du compilateur, mais pas une erreur. Les analyseurs de code peuvent également parfois aider. En C #, de telles erreurs sont presque impossibles, l' if(...) et similaires nécessitent un type bool , vous ne pouvez pas mélanger des types bool et numériques dans des expressions arithmétiques.


Comment se battre:


  • Programme sans avertissements. Malheureusement, cela n'aide pas toujours; certaines des erreurs dĂ©crites ci-dessus ne donnent pas d'avertissement.
  • Utilisez des analyseurs de code statique.
  • Technique de rĂ©ception Ă  l'ancienne: lors de la comparaison avec une constante, placez-la Ă  gauche, par exemple if(MAX_PATH==x) . Il a l'air assez copropriĂ©tĂ© (et a mĂȘme son propre nom - "notation Yoda"), et aide dans un petit nombre de cas.
  • Utilisez le qualificatif const aussi largement que possible. Encore une fois, cela n'aide pas toujours.
  • Prenez l'habitude d'Ă©crire les expressions logiques correctes: if(x!=0) au lieu de if(x) . (Bien que vous puissiez tomber dans le piĂšge des prioritĂ©s des opĂ©rateurs ici, consultez le troisiĂšme exemple.)
  • Soyez extrĂȘmement attentif.


1.2. Conversions implicites


C ++ fait référence à des langages fortement typés, mais les conversions de types implicites sont largement utilisées pour raccourcir le code. Ces conversions implicites peuvent dans certains cas conduire à des erreurs.


Les conversions implicites les plus ennuyeuses sont les conversions d'un type numĂ©rique ou d'un pointeur vers bool et de bool vers int . Ce sont ces transformations (nĂ©cessaires Ă  la compatibilitĂ© avec C) qui provoquent les problĂšmes dĂ©crits dans la section 1.1. Les conversions implicites qui peuvent potentiellement entraĂźner une perte de prĂ©cision des donnĂ©es numĂ©riques (rĂ©trĂ©cissement des conversions), par exemple du double au int sont pas toujours appropriĂ©es non plus. Dans de nombreux cas, le compilateur gĂ©nĂšre un avertissement (surtout lorsqu'il peut y avoir une perte de prĂ©cision des donnĂ©es numĂ©riques), mais un avertissement n'est pas une erreur. En C #, les conversions entre types numĂ©riques et bool interdites (mĂȘme explicites), et les conversions qui peuvent potentiellement entraĂźner une perte de prĂ©cision dans les donnĂ©es numĂ©riques sont presque toujours une erreur.


Le programmeur peut ajouter d'autres conversions implicites: (1) définir un constructeur avec un paramÚtre sans le mot-clé explicit ; (2) la définition d'un opérateur de conversion de type. Ces transformations comblent des lacunes de sécurité supplémentaires basées sur des principes de typage solides.


En C #, le nombre de conversions implicites intĂ©grĂ©es est beaucoup plus petit; les conversions implicites personnalisĂ©es doivent ĂȘtre dĂ©clarĂ©es Ă  l'aide du mot clĂ© implicit .


Comment se battre:


  • Programme sans avertissements.
  • Faites trĂšs attention aux modĂšles dĂ©crits ci-dessus, ne les utilisez pas sans besoin extrĂȘme.


2. Résolution du nom



2.1. Masquage de variables dans des étendues imbriquées


En C ++, la rĂšgle suivante s'applique. Soit


 //   {    int x;    // ... //  ,       {        int x;        // ...    } } 

Selon les rĂšgles C ++, la variable dĂ©clarĂ©e en cache la variable dĂ©clarĂ©e en La premiĂšre dĂ©claration x ne doit pas nĂ©cessairement ĂȘtre dans un bloc: elle peut ĂȘtre membre d'une classe ou d'une variable globale, elle doit juste ĂȘtre visible dans le bloc


Imaginez maintenant la situation oĂč vous devez refactoriser le code suivant


 //   {    int x;    // ... //  ,       {    // -         } } 

Par erreur, des modifications sont apportées:


 //   {    //  , :    int x;    // -         // ...    //  :    // -      } 

Et maintenant, le code «quelque chose se fait avec de » fera quelque chose avec de ! Il est clair que tout ne fonctionne pas comme avant, et de trouver ce qui est souvent trÚs difficile. Ce n'est pas en vain qu'en C # il est interdit de cacher les variables locales (bien que les membres de la classe le puissent). Notez que le mécanisme de masquage des variables sous une forme ou une autre est utilisé dans presque tous les langages de programmation.


Comment se battre:


  • DĂ©clarez les variables dans la portĂ©e la plus Ă©troite possible.
  • N'Ă©crivez pas de blocs longs et profondĂ©ment imbriquĂ©s.
  • Utilisez des conventions de codage pour distinguer visuellement les identifiants de portĂ©e diffĂ©rente.
  • Soyez extrĂȘmement attentif.


2.2. Surcharge de fonction


La surcharge de fonctions fait partie intĂ©grante de nombreux langages de programmation et C ++ ne fait pas exception. Mais cette opportunitĂ© doit ĂȘtre utilisĂ©e avec prĂ©caution, sinon vous pouvez avoir des ennuis. Dans certains cas, par exemple, lorsque le constructeur est surchargĂ©, le programmeur n'a pas le choix, mais dans d'autres cas, le refus de surcharger peut ĂȘtre justifiĂ©. Tenez compte des problĂšmes qui surviennent lors de l'utilisation de fonctions surchargĂ©es.


Si vous essayez de considĂ©rer toutes les options possibles pouvant survenir lors de la rĂ©solution d'une surcharge, les rĂšgles de rĂ©solution d'une surcharge s'avĂšrent ĂȘtre trĂšs compliquĂ©es, et donc difficiles Ă  prĂ©voir. La complexitĂ© supplĂ©mentaire est introduite par les fonctions de modĂšle et la surcharge des opĂ©rateurs intĂ©grĂ©s. C ++ 11 a ajoutĂ© des problĂšmes avec les liens rvalue et les listes d'initialisation.


Des problĂšmes peuvent ĂȘtre créés par l'algorithme de recherche pour que les candidats rĂ©solvent la surcharge dans les zones de visibilitĂ© imbriquĂ©es. Si le compilateur a trouvĂ© des candidats dans la portĂ©e actuelle, la recherche se termine. Si les candidats trouvĂ©s ne sont pas appropriĂ©s, conflictuels, supprimĂ©s ou inaccessibles, une erreur est gĂ©nĂ©rĂ©e, mais aucune autre recherche n'est tentĂ©e. Et seulement s'il n'y a aucun candidat dans la portĂ©e actuelle, la recherche passe Ă  la portĂ©e suivante, plus large. Le mĂ©canisme de masquage du nom fonctionne, qui est presque le mĂȘme que celui discutĂ© dans la section 2.1, voir [Dewhurst].


Les fonctions de surcharge peuvent réduire la lisibilité du code, ce qui signifie provoquer des erreurs.


L'utilisation de fonctions avec des paramÚtres par défaut ressemble à l'utilisation de fonctions surchargées, bien que, bien sûr, il y ait moins de problÚmes potentiels. Mais le problÚme de la mauvaise lisibilité et des erreurs possibles subsiste.


Avec une extrĂȘme prudence, les paramĂštres de surcharge et par dĂ©faut pour les fonctions virtuelles doivent ĂȘtre utilisĂ©s, voir la section 5.2.


C # prend également en charge la surcharge de fonctions, mais les rÚgles de résolution des surcharges sont légÚrement différentes.


Comment se battre:


  • N'abusez pas de la surcharge de fonctions, ainsi que de la conception de fonctions avec des paramĂštres par dĂ©faut.
  • Si les fonctions sont surchargĂ©es, utilisez des signatures qui ne font aucun doute lors de la rĂ©solution des surcharges.
  • Ne dĂ©clarez pas les fonctions du mĂȘme nom dans la portĂ©e imbriquĂ©e.
  • N'oubliez pas que le mĂ©canisme des fonctions distantes ( =delete ) apparu en C ++ 11 peut ĂȘtre utilisĂ© pour interdire certaines options de surcharge.


3. Constructeurs, destructeurs, initialisation, suppression



3.1. Fonctions membres de classe générées par le compilateur


Si le programmeur n'a pas dĂ©fini les fonctions membres de la classe dans la liste suivante - le constructeur par dĂ©faut, le constructeur de copie, l'opĂ©rateur d'affectation de copie, le destructeur - le compilateur peut le faire pour lui. C ++ 11 a ajoutĂ© un constructeur de dĂ©placement et un opĂ©rateur d'affectation de dĂ©placement Ă  cette liste. Ces fonctions membres sont appelĂ©es fonctions membres spĂ©ciales. Ils ne sont gĂ©nĂ©rĂ©s que s'ils sont utilisĂ©s et des conditions supplĂ©mentaires spĂ©cifiques Ă  chaque fonction sont remplies. Nous attirons l'attention sur le fait que cette utilisation peut s'avĂ©rer assez cachĂ©e (par exemple lors de l'implĂ©mentation de l'hĂ©ritage). Si la fonction requise ne peut pas ĂȘtre gĂ©nĂ©rĂ©e, une erreur est gĂ©nĂ©rĂ©e. (À l'exception des opĂ©rations de relocalisation, elles sont remplacĂ©es par des opĂ©rations de copie.) Les fonctions membres gĂ©nĂ©rĂ©es par le compilateur sont publiques et intĂ©grables. Des dĂ©tails sur les fonctions spĂ©ciales des membres peuvent ĂȘtre trouvĂ©s dans [Meyers2].


Dans certains cas, une telle aide du compilateur peut ĂȘtre un «service baissier». L'absence de fonctions membres spĂ©ciales personnalisĂ©es peut conduire Ă  la crĂ©ation d'un type trivial, ce qui, Ă  son tour, provoque le problĂšme des variables non initialisĂ©es, voir la section 3.2. Les fonctions membres gĂ©nĂ©rĂ©es sont publiques, ce qui n'est pas toujours cohĂ©rent avec la conception des classes. Dans les classes de base, le constructeur doit ĂȘtre protĂ©gĂ©; parfois, pour un contrĂŽle plus fin du cycle de vie de l'objet, un destructeur protĂ©gĂ© est nĂ©cessaire. Si une classe a un descripteur de ressource brute en tant que membre et possĂšde cette ressource, le programmeur doit alors implĂ©menter le constructeur de copie, l'opĂ©rateur d'affectation de copie et le destructeur. La soi-disant «rĂšgle des trois grands» est bien connue, qui stipule que si un programmeur dĂ©finit au moins une des trois opĂ©rations - constructeur de copie, opĂ©rateur d'affectation de copie ou destructeur - alors il doit dĂ©finir les trois opĂ©rations. Le constructeur de dĂ©placement et l'opĂ©rateur d'affectation de dĂ©placement gĂ©nĂ©rĂ© par le compilateur sont Ă©galement loin de toujours ce dont vous avez besoin. Le destructeur gĂ©nĂ©rĂ© par le compilateur conduit dans certains cas Ă  des problĂšmes trĂšs subtils, pouvant entraĂźner une fuite de ressources, voir section 3.7.


Le programmeur peut interdire la génération de fonctions membres spéciales, en C ++ 11 il est nécessaire d'utiliser la construction "=delete" lors de la déclaration, en C ++ 98 déclarer la fonction membre correspondante privée et non définir.


Si le programmeur est à l'aise avec les fonctions membres générées par le compilateur, alors en C ++ 11, il peut l'indiquer explicitement, et pas simplement supprimer la déclaration. Pour ce faire, vous devez utiliser la construction "=default" lors de la déclaration, tandis que le code est mieux lu et que des fonctionnalités supplémentaires apparaissent liées à la gestion du niveau d'accÚs.


En C #, le compilateur peut générer un constructeur par défaut, cela ne pose généralement aucun problÚme.


Comment se battre:


  • ContrĂŽlez le compilateur gĂ©nĂ©rant des fonctions membres spĂ©ciales. Si nĂ©cessaire, mettez-les en Ɠuvre vous-mĂȘme ou interdisez-les.


3.2. Variables non initialisées


Les constructeurs et les destructeurs peuvent ĂȘtre appelĂ©s Ă©lĂ©ments clĂ©s du modĂšle d'objet C ++. Lors de la crĂ©ation d'un objet, le constructeur doit ĂȘtre appelĂ© et lors de la suppression, le destructeur est appelĂ©. Mais les problĂšmes de compatibilitĂ© avec C ont forcĂ© certaines exceptions, et cette exception est appelĂ©e types triviaux. Ils sont introduits pour simuler les types sichny et le cycle de vie syshny des variables, sans l'appel obligatoire du constructeur et du destructeur. Le code C, s'il est compilĂ© et exĂ©cutĂ© en C ++, devrait fonctionner comme en C. Les types triviaux incluent les types numĂ©riques, les pointeurs, les Ă©numĂ©rations, ainsi que les classes, les structures, les unions et les tableaux constituĂ©s de types triviaux. Les classes et les structures doivent remplir des conditions supplĂ©mentaires: absence de constructeur personnalisĂ©, destructeur, copie, fonctions virtuelles. Pour une classe triviale, le compilateur peut gĂ©nĂ©rer un constructeur par dĂ©faut et un destructeur. Le constructeur par dĂ©faut met Ă  zĂ©ro l'objet, le destructeur ne fait rien. Mais ce constructeur ne sera gĂ©nĂ©rĂ© et utilisĂ© que s'il est explicitement appelĂ© lors de l'initialisation de la variable. Une variable de type trivial ne sera pas initialisĂ©e si vous n'utilisez pas une variante d'initialisation explicite. La syntaxe d'initialisation dĂ©pend du type et du contexte de la dĂ©claration de variable. Les variables statiques et locales sont initialisĂ©es lorsqu'elles sont dĂ©clarĂ©es. Pour une classe, les classes de base immĂ©diates et les membres de classe non statiques sont initialisĂ©s dans la liste d'initialisation du constructeur. (C ++ 11 vous permet d'initialiser des membres de classe non statiques lors de la dĂ©claration, voir plus loin.) Pour les objets dynamiques, l'expression new T() crĂ©e un objet initialisĂ© par le constructeur par dĂ©faut, mais le new T pour les types triviaux crĂ©e un objet non initialisĂ©. Lors de la crĂ©ation d'un tableau dynamique d'un type trivial, new T[N] , ses Ă©lĂ©ments seront toujours non initialisĂ©s. Si une instance de std::vector<T> est créée ou Ă©tendue et que les paramĂštres ne sont pas fournis pour l'initialisation explicite des Ă©lĂ©ments, ils sont garantis pour appeler le constructeur par dĂ©faut. C ++ 11 introduit une nouvelle syntaxe d'initialisation - utilisant des accolades. Une paire de crochets vide signifie l'initialisation Ă  l'aide du constructeur par dĂ©faut. Une telle initialisation est possible partout oĂč l'initialisation traditionnelle est utilisĂ©e, en plus il est devenu possible d'initialiser des membres non statiques de la classe lors de la dĂ©claration, ce qui remplace l'initialisation dans la liste d'initialisation du constructeur.


Une variable non initialisée est structurée comme suit: si elle est définie dans la portée de l' namespace (globalement), elle aura tous les bits zéro, si elle est locale ou créée dynamiquement, elle recevra un ensemble aléatoire de bits. Il est clair que l'utilisation d'une telle variable peut conduire à un comportement imprévisible du programme.


Certes, la progression ne s'arrĂȘte pas, les compilateurs modernes, dans certains cas, dĂ©tectent les variables non initialisĂ©es et gĂ©nĂšrent une erreur. Les analyseurs de code non initialisĂ©s dĂ©tectent encore mieux.


La bibliothĂšque standard C ++ 11 possĂšde des modĂšles appelĂ©s propriĂ©tĂ©s de type (fichier d'en-tĂȘte <type_traits> ). L'un d'eux vous permet de dĂ©terminer si le type est trivial. L'expression std::is_trivial<>::value est true si T type trivial et false sinon.


Les structures sysyliques sont aussi souvent appelées Plain Old Data (POD). Nous pouvons supposer que POD et le «type trivial» sont des termes presque équivalents.


En C #, les variables non initialisĂ©es provoquent une erreur qui est contrĂŽlĂ©e par le compilateur. Les champs d'objets d'un type de rĂ©fĂ©rence sont initialisĂ©s par dĂ©faut si l'initialisation explicite n'est pas effectuĂ©e. Les champs d'objets d'un type significatif sont initialisĂ©s soit tous par dĂ©faut, soit tous doivent ĂȘtre initialisĂ©s explicitement.


Comment se battre:


  • Prenez l'habitude d'initialiser explicitement une variable. Une variable non initialisĂ©e devrait "couper l'Ɠil".
  • DĂ©clarez les variables dans la portĂ©e la plus Ă©troite possible.
  • Utilisez des analyseurs de code statique.
  • Ne concevez pas de types triviaux. Pour s'assurer que le type n'est pas trivial, il suffit de dĂ©finir un constructeur personnalisĂ©.


3.3. Procédure d'initialisation pour les classes de base et les membres de classe non statiques


Lors de l'implĂ©mentation du constructeur de classe, les classes de base immĂ©diates et les membres de classe non statiques sont initialisĂ©s. L'ordre d'initialisation est dĂ©terminĂ© par la norme: d'abord, les classes de base dans l'ordre dans lequel elles sont dĂ©clarĂ©es dans la liste des classes de base, puis les membres non statiques de la classe dans l'ordre de dĂ©claration. Si nĂ©cessaire, l'initialisation explicite des classes de base et des membres non statiques utilise la liste d'initialisation du constructeur. Malheureusement, les Ă©lĂ©ments de cette liste ne doivent pas nĂ©cessairement ĂȘtre dans l'ordre dans lequel l'initialisation a lieu. Ceci doit ĂȘtre pris en compte si, lors de l'initialisation, les Ă©lĂ©ments de liste utilisent des rĂ©fĂ©rences Ă  d'autres Ă©lĂ©ments de liste. En cas d'erreur, le lien peut ĂȘtre vers un objet qui n'a pas encore Ă©tĂ© initialisĂ©. C ++ 11 vous permet d'initialiser des membres de classe non statiques lors de la dĂ©claration (en utilisant des accolades). Dans ce cas, ils n'ont pas besoin d'ĂȘtre initialisĂ©s dans la liste d'initialisation du constructeur et le problĂšme est partiellement supprimĂ©.


En C #, un objet est initialisĂ© comme suit: d'abord les champs sont initialisĂ©s, du sous-objet de base Ă  la derniĂšre dĂ©rivĂ©e, puis les constructeurs sont appelĂ©s dans le mĂȘme ordre. Le problĂšme dĂ©crit ne se produit pas.


Comment se battre:


  • Conservez la liste d'initialisation du constructeur dans l'ordre de dĂ©claration.
  • Essayez de rendre l'initialisation des classes de base et des membres de classe indĂ©pendants.
  • Utilisez l'initialisation des membres non statiques lors de la dĂ©claration.


3.4. Procédure d'initialisation pour les membres de classe statique et les variables globales


Les membres de classe statiques, ainsi que les variables dĂ©finies dans l' namespace portĂ©e (globalement) dans diffĂ©rentes unitĂ©s de compilation (fichiers), sont initialisĂ©s dans l'ordre dĂ©terminĂ© par l'implĂ©mentation. Ceci doit ĂȘtre pris en compte si, lors de l'initialisation, ces variables utilisent des rĂ©fĂ©rences les unes aux autres. Le lien peut ĂȘtre vers une variable non initialisĂ©e.


Comment se battre:


  • Prenez des mesures spĂ©ciales pour Ă©viter cette situation. Par exemple, utilisez des variables statiques locales (singleton), elles sont initialisĂ©es Ă  la premiĂšre utilisation.


3.5. Exceptions dans les destructeurs


Le destructeur ne doit pas lever d'exceptions. Si vous enfreignez cette rÚgle, vous pouvez obtenir un comportement indéfini, le plus souvent une résiliation anormale.


Comment se battre:


  • Évitez de lancer des exceptions dans le destructeur.


3.6. Suppression d'objets et de tableaux dynamiques


Si un objet dynamique d'un type T


 T* pt = new T(/* ... */); 

puis il est supprimé avec l'opérateur de delete


 delete pt; 

Si un tableau dynamique est créé


 T* pt = new T[N]; 

puis il est supprimé avec l'opérateur delete[]


 delete[] pt; 

Si vous ne respectez pas cette rÚgle, vous pouvez obtenir un comportement indéfini, c'est-à-dire que tout peut arriver: une fuite de mémoire, un crash, etc. Voir [Meyers1] pour plus de détails.


Comment se battre:


  • Utilisez le bon formulaire de delete .


3.7. Suppression lorsque la déclaration de classe est incomplÚte


Le caractĂšre omnivore de l'opĂ©rateur de delete peut crĂ©er certains problĂšmes; il peut ĂȘtre appliquĂ© Ă  un pointeur de type void* ou Ă  un pointeur sur une classe qui a une dĂ©claration incomplĂšte (prĂ©emptive). L'opĂ©rateur de delete appliquĂ© Ă  un pointeur sur une classe est une opĂ©ration en deux phases; d'abord, le destructeur est appelĂ©, puis la mĂ©moire est libĂ©rĂ©e.Si l'opĂ©rateur est appliquĂ© deleteĂ  un pointeur sur une classe avec une dĂ©claration incomplĂšte, aucune erreur ne se produit, le compilateur ignore simplement l'appel au destructeur (bien qu'un avertissement soit Ă©mis). Prenons un exemple:


 class X; //   X* CreateX(); void Foo() {    X* p = CreateX();    delete p; } 

Ce code compile mĂȘme si la deletedĂ©claration de classe complĂšte n'est pas disponible au cadran-pair X. Visual Studio affiche l'avertissement suivant:

warning C4150: deletion of pointer to incomplete type 'X'; no destructor called


S'il existe une implémentation Xet que CreateX()le code est compilé, s'il CreateX()renvoie un pointeur vers un objet créé par l'opérateur new, alors l'appel est Foo()exécuté avec succÚs, le destructeur n'est pas appelé. Il est clair que cela peut conduire à un épuisement des ressources, donc encore une fois sur la nécessité de traiter soigneusement les avertissements.


, -. , . , , , , . [Meyers2].


:


  • .
  • .
  • .


4. ,



4.1.


++ , . . . , 1.1.


Voici un exemple:


 std::out<<c?x:y; 


 (std::out<<c)?x:y; 


 std::out<<(c?x:y); 

, , .


. << ?: std::out void* . ++ , . -, , . ?: . , ( ).


: x&f==0 x&(f==0) , (x&f)==0 , , , . - , , , , .


. / . / , /, . , x/4+1 x>>2+1 , x>>(2+1) , (x>>2)+1 , .


C# , C++, , - .


:


  • , . , , .


4.2.


++ , . . , , . 4.1. — + += . . , : , (), && , || . , (-), (short-circuit evaluation semantics), , . & ( ). & , .. .


, - (-) , . .


- , , . . [Dewhurst].


C# , , , .


:


  • .
  • .


4.3.


++ , . ( : , (), && , || , ?: .) , , , . :


 int x=0; int y=(++x*2)+(++x*3); 

y .


, . .

 class X; class Y; void Foo(std::shared_ptr<X>, std::shared_ptr<Y>); 

Foo() :


 Foo(std::shared_ptr<X>(new X()), std::shared_ptr<Y>(new Y())); 

: X , Y , std::shared_ptr<X> , std::shared_ptr<Y> . Y , X .


:


 auto p1 = std::shared_ptr<X>(new X()); auto p2 = std::shared_ptr<Y>(new Y()); Foo(p1, p2); 

std::make_shared<Y> ( , ):


 Foo(std::make_shared<X>(), std::make_shared<Y>()); 

. [Meyers2].


:


  • .


5.



5.1.


++98 , ( ), , ( , ). virtual , , . ( ), , , . , , . , ++11 override , , , . .


:


  • override .
  • . , .


5.2.


. , , . . . [Dewhurst].


:


  • .


5.3.


, , . , , post_construct pre_destroy. , — . . , : ( ) . (, , .) , ( ), ( ). . [Dewhurst]. , , .


— - .


, C# , , , . C# : , , . , ( , ).


:


  • , , .


5.4.


, , delete . , - .


:


  • .


6.


— C/C++, . . . « ».


C# unsafe mode, .



6.1.


/++ , : strcpy() , strcat() , sprinf() , etc. ( std::vector<> , etc.) , . (, , , . . Checked Iterators MSDN.) , : , , ; , .


C#, unsafe mode, .


:


  • , .
  • .
  • z-terminated , _s (. ).


6.2. Z-terminated


, . , :


 strncpy(dst,src,n); 

strlen(src)>=n , dst (, ). , , . . — . if(*str) , if(strlen(str)>0) , . [Spolsky].


C# string .


:


  • .
  • z-terminated , _s (. ).


6.3.


... . printf - , C. , , , , . , .


C# printf , .


:


  • . , printf - /.
  • .


7.



7.1.


++ , , , . Voici un exemple:


 const int N = 4, M = 6; int x,                // 1    *px,              // 2    ax[N],            // 3    *apx[N],          // 4    F(char),          // 5    *G(char),          // 6    (*pF)(char),      // 7    (*apF[N])(char),  // 8    (*pax)[N],        // 9    (*apax[M])[N],    // 10    (*H(char))(long);  // 11 

:


  1. int ;
  2. int ;
  3. N int ;
  4. N int ;
  5. , char int ;
  6. , char int ;
  7. , char int ;
  8. N , char int ;
  9. N int ;
  10. M N int ;
  11. , char , long int .

, . ( .)


* & . ( .)


typedef ( using -). , :


 typedef int(*P)(long); PH(char); 

, .


C# , .


:


  • .


7.2.


.


 class X { public:    X(int val = 0); // ... }; 


 X x(5); 

x X , 5.


 X x(); 

x , X , x X , . X , , :


 X x; X x = X(); X x{};    //   C++11 

, , , . [Sutter].


, , C++ ( ). . ( C++ .)


, , , , .


C# , , .


:


  • .


8.



8.1. inline ODR


, inline — . , . inline (One Defenition Rule, ODR). . , . , ODR. static : , , . static inline . , , ODR, . , . - , -. .


:


  • «» inline . namespace . , .
  • — namespace .


8.2.


. . , , , , .


:


  • , .
  • , : () , -.
  • using -: using namespace , using -.
  • .


8.3. switch


— break case . ( .) C# .


:


  • .


8.4.


++ , — , — . ( class struct ) , . ( , # Java.) — , .


  1. , . ( std::string , std::vector , etc.), , .
  2. , , .
  3. , (slicing), , .

, , , . . , , . , . . — ( =delete ), — explicit .


C# , .


:


  • , .
  • .


8.5. Gestion des ressources


++ . , . - ( ), ++11 , , , .


C++ .


C# , . , . (using-) Basic Dispose.


:


  • - .


8.6.


«» . , , C++ , STL- - .


. . , . . «», . COM- . (, .) , C++ . — . . . , («» ) , . .


# , . — .


:


  • .
  • .


8.7.


C++ , : , , . ( !) . , . , . , , . (, .)


C ( ), C++ C ( extern "C" ). C/C++ .


-. #pragma - , , .


, , , .


, , COM. COM-, , ( , ). COM , , .


C# . , — , C#, C# C/C++.


:


  • .


8.8.


, . , . C++ . Au lieu de cela


 #define XXL 32 


 const int XXL=32; 

. inline .


# ( ).


:


  • .


9.


  1. . . . , .
  2. .
  3. . ++ — ++11/14/17.
  4. - , - .
  5. .


Les références


[Dewhurst]
, . C++. .: . de l'anglais — .: , 2012.


[Meyers1]
, . C++. 55 .: . de l'anglais — .: , 2014.


[Meyers2]
, . C++: 42 C++11 C++14.: . de l'anglais — .: «.. », 2016.


[Sutter]
, . C++.: . de l'anglais — : «.. », 2015.


[Spolsky]
, . .: . de l'anglais — .: -, 2008.




Source: https://habr.com/ru/post/fr428898/


All Articles