Aide-mémoire de l'abréviation C ++ et plus encore. Partie 1: C ++

Une fois, j'ai Ă©tĂ© interviewĂ© pour le poste de dĂ©veloppeur C ++ dans un bureau dĂ©cent et mĂȘme bien connu. J'avais dĂ©jĂ  une certaine expĂ©rience Ă  l'Ă©poque, on m'appelait mĂȘme un dĂ©veloppeur leader chez mon employeur Ă  l'Ă©poque. Mais lorsqu'on m'a demandĂ© si je savais des choses telles que DRY, KISS, YAGNI, NIH, j'ai dĂ» rĂ©pondre «Non» encore et encore.

J'ai Ă©chouĂ© lamentablement, bien sĂ»r. Mais alors les abrĂ©viations ci-dessus ont Ă©tĂ© googlĂ© et mĂ©morisĂ©es. En lisant des articles et des livres thĂ©matiques, en me prĂ©parant pour des interviews et en parlant avec des collĂšgues, j'ai appris de nouvelles choses, les ai oubliĂ©es, googlĂ© Ă  nouveau et compris. Il y a quelques mois, un de mes collĂšgues a mentionnĂ© avec dĂ©sinvolture dans le chat de travail IIFE dans le contexte de C ++. Comme ce grand-pĂšre dans une blague, je suis presque tombĂ© du poĂȘle et je suis de nouveau entrĂ© dans Google.



C'est alors que j'ai décidé de composer (principalement pour moi) une feuille de triche pour les abréviations qui sont utiles pour un développeur C ++. Cela ne signifie pas qu'ils s'appliquent uniquement au C ++, ou qu'ils sont des concepts tout-tout de C ++ (vous pouvez écrire des volumes sur les idiomes du langage). Non, ce ne sont que des concepts que j'ai réellement rencontrés dans le travail et les entretiens, généralement exprimés sous forme d'abréviations. Eh bien, j'ai raté des choses absolument triviales comme LIFO, FIFO, CRUD, OOP, GCC et MSVC.

Néanmoins, les abréviations sont apparues décemment, j'ai donc divisé la feuille de triche en 2 parties: fortement caractéristique du C ++ et plus courante. Quand c'était approprié, j'ai regroupé les concepts, sinon je les ai simplement listés par ordre alphabétique. En général, leur ordre n'a pas beaucoup de sens.

Choses de base:
‱ ODR
‱ POD
‱ POF
‱ PIMPL
‱ RAII
‱ RTTI
‱ STL
‱ UB

Subtilités de la langue:
‱ ADL
‱ CRTP
‱ CTAD
‱ EBO
‱ IIFE
‱ NVI
‱ RVO et NRVO
‱ SFINAE
‱ SBO, SOO, SSO

MISE À JOUR:
‱ CV
‱ LTO
‱ PCH
‱ PGO
‱ SEH / VEH
‱ TMP
‱ VLA

Choses de base


ODR


Une rÚgle de définition. La rÚgle d'une définition. Simplifié signifie ce qui suit:

  • Au sein d'une mĂȘme unitĂ© de traduction, chaque variable, fonction, classe, etc., ne peut avoir plus d'une dĂ©finition. Il y a autant de publicitĂ©s que possible (sauf pour les transferts sans type de base donnĂ©, qui ne peuvent tout simplement pas ĂȘtre dĂ©clarĂ©s sans dĂ©finition), mais pas plus d'une dĂ©finition. Moins possible si l'entitĂ© n'est pas utilisĂ©e.
  • Tout au long du programme, chaque fonction et variable non intĂ©grĂ©e doit avoir exactement une dĂ©finition. Chaque fonction en ligne et variable utilisĂ©e doit avoir une dĂ©finition dans chaque unitĂ© de traduction.
  • Certaines entitĂ©s - par exemple, les classes, les fonctions en ligne et une variable, les modĂšles, les Ă©numĂ©rations, etc. - peuvent avoir plusieurs dĂ©finitions dans un programme (mais pas plus d'une dans une unitĂ© de traduction). En fait, cela se produit lorsque le mĂȘme en-tĂȘte contenant une classe entiĂšrement implĂ©mentĂ©e, par exemple, est connectĂ© Ă  plusieurs fichiers .cpp. Mais ces dĂ©finitions devraient coĂŻncider (je simplifie grandement, mais l'essentiel est le suivant). Sinon, ce sera UB .

Le compilateur détectera facilement une violation ODR dans une unité de traduction. Mais il ne pourra rien faire si la rÚgle est violée à l'échelle d'un programme - ne serait-ce que parce que le compilateur traite une unité de traduction à la fois.

L'éditeur de liens peut trouver beaucoup plus de violations, mais, à proprement parler, il n'est pas obligé de le faire (car, selon la norme, UB est ici) et il peut manquer quelque chose. En outre, le processus de recherche de violations ODR au stade de la liaison est d'une complexité quadratique et l'assemblage de code C ++ n'est pas si rapide.

En consĂ©quence, la principale responsabilitĂ© du respect de cette rĂšgle (en particulier au niveau du programme) incombe au dĂ©veloppeur lui-mĂȘme. Et oui - seules les entitĂ©s avec un lien externe peuvent violer le RLL Ă  l'Ă©chelle du programme; ceux de l'intĂ©rieur (c'est-Ă -dire dĂ©finis dans des espaces de noms anonymes) ne participent pas Ă  ce carnaval.

En savoir plus: une fois (anglais) , deux (anglais)

Pod


Plain Old Data. Structure de donnĂ©es simple. La dĂ©finition la plus simple: c'est une telle structure que vous pouvez, comme elle est, sous forme binaire envoyer / recevoir de la bibliothĂšque C. Ou, ce qui est la mĂȘme chose, copiez correctement avec simple memcpy .

De Standard à Standard, la définition complÚte a changé en détail. Le dernier POD C ++ 17 définit actuellement comment

  • type scalaire
  • ou une classe / structure / union qui:
    - il y a une classe triviale
    - il y a une classe avec un appareil standard
    - ne contient pas de champs non statiques non POD
  • ou un tableau de ces types

Classe triviale:

  • en a au moins un non supprimĂ©:
    - constructeur par défaut
    - constructeur de copie
    - constructeur en mouvement
    - opérateur d'affectation de copie
    - opérateur d'affectation de déplacement
  • tous les constructeurs par dĂ©faut qui copient et dĂ©placent les constructeurs et les opĂ©rateurs d'affectation sont triviaux (simplifiĂ©s - gĂ©nĂ©rĂ©s par le compilateur) ou distants
  • a un destructeur non distant trivial
  • tous les types de base et tous les champs des types de classe ont des destructeurs triviaux
  • pas de mĂ©thodes virtuelles (y compris destructeur)
  • aucun type de base virtuelle

Classe avec un appareil standard (classe de mise en page standard):

  • pas de mĂ©thodes virtuelles
  • aucun type de base virtuelle
  • aucun champ de lien non statique
  • tous les champs non statiques ont le mĂȘme modificateur d'accĂšs (public / protĂ©gĂ© / privĂ©)
  • tous les champs et classes de base non statiques sont Ă©galement des types avec un pĂ©riphĂ©rique standard
  • tous les champs non statiques de la classe elle-mĂȘme et tous ses ancĂȘtres sont dĂ©clarĂ©s dans une seule classe (c'est-Ă -dire dans la classe elle-mĂȘme ou dans l'un des ancĂȘtres)
  • Il n'hĂ©rite pas deux fois du mĂȘme type, c'est-Ă -dire qu'il est impossible de le faire:
     struct A {}; struct B : A {}; struct C : A{}; struct D : B, C {}; 
  • le type du premier champ non statique ou, s'il s'agit d'un tableau, le type de son Ă©lĂ©ment ne doit coĂŻncider avec aucun des types de base (en raison de l' EBO obligatoire dans ce cas)

Cependant, en C ++ 20, il n'y aura plus de concept de type POD , seuls le type trivial et le type avec le périphérique standard resteront.

En savoir plus: un (russe) , deux (anglais) , trois (anglais)

POF


Fonction ancienne simple. Une fonction simple de style C. Mentionnée dans la norme avant C ++ 14 inclus uniquement dans le contexte des gestionnaires de signaux. Les exigences pour cela sont:

  • n'utilise que des choses communes Ă  C et C ++ (c'est-Ă -dire pas d'exceptions et try-catch , par exemple)
  • ne provoque pas directement ou indirectement des fonctions non POF , Ă  l'exception des opĂ©rations sans bloc atomique ( std::atomic_init , std::atomic_fetch_add , etc.)

Seules ces fonctions, qui ont Ă©galement une liaison C ( extern "C" ), sont autorisĂ©es par la norme Ă  ĂȘtre utilisĂ©es comme gestionnaires de signaux. La prise en charge d'autres fonctions dĂ©pend du compilateur.

En C ++ 17, le concept de POF disparaßt, au lieu de cela apparaßt une évaluation sûre du signal. Dans ces calculs sont interdits:

  • appels Ă  toutes les fonctions de la bibliothĂšque standard, sauf atomique, sans verrouillage
  • new appels et delete appels
  • en utilisant dynamic_cast
  • appel Ă  l'entitĂ© thread_local
  • tout travail avec des exceptions
  • initialisation d'une variable statique locale
  • attendre la fin de l'initialisation de la variable statique

Si le gestionnaire de signal fait l'une des actions ci-dessus, la norme promet UB .

Lire la suite: le temps (anglais)

PIMPL


Pointeur vers la mise en Ɠuvre. Pointeur vers l'implĂ©mentation. L'idiome classique en C ++, Ă©galement connu sous le nom de d-pointer, opaque pointer, compilation firewall. Cela consiste dans le fait que toutes les mĂ©thodes privĂ©es, les champs et autres dĂ©tails d'implĂ©mentation d'une certaine classe sont allouĂ©s dans une classe distincte, et seules les mĂ©thodes publiques (c'est-Ă -dire une interface) et un pointeur vers une instance de cette nouvelle classe sĂ©parĂ©e restent dans la classe d'origine. Par exemple:

foo.hpp
 class Foo { public: Foo(); ~Foo(); void doThis(); int doThat(); private: class Impl; std::unique_ptr<Impl> pImpl_; }; 


foo.cpp
 #include "foo.hpp" class Foo::Impl { // implementation }; Foo::Foo() : pImpl_(std::make_unique<Impl>()) {} Foo::~Foo() = default; void Foo::doThis() { pImpl_->doThis(); } int Foo::doThat() { return pImpl_->doThat(); } 


Pourquoi est-ce nécessaire, c'est-à-dire avantages:

  • Encapsulation: les utilisateurs de la classe via la connexion d'en-tĂȘte ne reçoivent que ce dont ils ont besoin - une interface publique. Si les dĂ©tails de l'implĂ©mentation changent, le code client n'a pas besoin d'ĂȘtre recompilĂ© (voir ABI ).
  • Temps de compilation: puisque l'en-tĂȘte public ne sait rien de l'implĂ©mentation, il n'inclut pas les nombreux en-tĂȘtes dont il a besoin. Par consĂ©quent, le nombre d'en-tĂȘtes connectĂ©s implicitement dans le code client est rĂ©duit. La recherche de noms et la rĂ©solution des surcharges est Ă©galement simplifiĂ©e, car l'en-tĂȘte public ne contient pas de membres privĂ©s (bien qu'ils soient privĂ©s, ils participent Ă  ces processus).

Prix, c'est-à-dire inconvénients:

  • Plus au moins un dĂ©rĂ©fĂ©rencement de pointeur et plus un appel de fonction lors de l'accĂšs aux mĂ©thodes publiques.
  • La taille de la classe de mĂ©moire nĂ©cessaire est augmentĂ©e de la taille du pointeur.
  • Une partie de cette mĂ©moire (probablement plus grande) est allouĂ©e sur le tas, ce qui affecte Ă©galement nĂ©gativement les performances.
  • La constance logique peut facilement ĂȘtre violĂ©e. Par exemple, un tel code compile:

     void Foo::doThis() const { pImpl_->doThis(); // cosnt method pImpl_->doSmthElse(); // non-const method } 

Certaines de ces lacunes peuvent ĂȘtre supprimĂ©es, mais le prix complique davantage le code et introduit des niveaux d'abstraction supplĂ©mentaires (voir FTSE ).

En savoir plus: un (russe) , deux (russe) , trois (anglais)

RAII


L'acquisition de ressources est l'initialisation. La capture d'une ressource est l'initialisation. Le sens de cet idiome est que la rĂ©tention d'une certaine ressource dure tout au long de la vie de l'objet correspondant. La capture de la ressource a lieu au moment de la crĂ©ation / initialisation de l'objet, la libĂ©ration - au moment de la destruction / finalisation du mĂȘme objet.

Curieusement (principalement pour les programmeurs C ++), cet idiome est Ă©galement utilisĂ© dans d'autres langages, mĂȘme ceux avec un garbage collector. En Java, c'est try-- , en Python l'instruction with , en C # la directive using , en Go the defer . Mais c'est en C ++ avec sa durĂ©e de vie des objets absolument prĂ©visible que RAII s'intĂšgre surtout organiquement.

En C ++, une ressource est généralement capturée dans le constructeur et libérée dans le destructeur. Par exemple, les pointeurs intelligents contrÎlent la mémoire de cette façon, les flux de fichiers gÚrent les fichiers, les verrous mutex verrouillent les mutex. La beauté est que, quelle que soit la façon dont le bloc est quitté (étendue) - est-ce normal à travers l'un des points de sortie, ou une exception a été levée - l'objet de contrÎle des ressources créé dans ce bloc sera détruit et la ressource sera libérée. C'est-à-dire En plus d'encapsuler RAII en C ++, il contribue également à assurer la sécurité au sens des exceptions.

Limitations, oĂč sans eux. Les destructeurs en C ++ ne renvoient pas de valeurs et ne devraient catĂ©goriquement pas lever d'exceptions. En consĂ©quence, si la libĂ©ration de la ressource s'accompagne de l'une ou l'autre, il sera nĂ©cessaire d'implĂ©menter une logique supplĂ©mentaire dans le destructeur de l'objet de contrĂŽle.

Lire la suite: une fois (russe) , deux (anglais)

RTTI


Informations sur le type d'exécution. Identification du type lors de l'exécution. Il s'agit d'un mécanisme permettant d'obtenir des informations sur le type d'un objet ou d'une expression lors de l'exécution. Il existe dans d'autres langages, mais en C ++ il est utilisé pour:

  • dynamic_cast
  • typeid et type_info
  • attraper l'exception

Une limitation importante: RTTI utilise une table de fonctions virtuelles, et ne fonctionne donc que pour les types polymorphes (un destructeur virtuel suffit). Une explication importante: dynamic_cast et typeid n'utilisent pas toujours RTTI , et fonctionnent donc pour les types non polymorphes. Par exemple, pour convertir dynamiquement un lien vers un descendant en un lien vers un ancĂȘtre, RTTI n'est pas nĂ©cessaire; toutes les informations sont disponibles au moment de la compilation.

RTTI n'est pas gratuit, quoique un peu, mais il affecte nĂ©gativement les performances et la taille de la mĂ©moire consommĂ©e (d'oĂč le conseil frĂ©quent de ne pas utiliser dynamic_cast raison de sa lenteur). Par consĂ©quent, les compilateurs, en rĂšgle gĂ©nĂ©rale, vous permettent de dĂ©sactiver RTTI . GCC et MSVC promettent que cela n'affectera pas l'exactitude des exceptions de capture.

Lire la suite: une fois (russe) , deux (anglais)

STL


BibliothÚque de modÚles standard. BibliothÚque de modÚles standard. Partie de la bibliothÚque standard C ++ qui fournit des conteneurs génériques, des itérateurs, des algorithmes et des fonctions d'assistance.

MalgrĂ© son nom bien connu, STL n'a jamais Ă©tĂ© ainsi appelĂ© dans la norme. À partir des sections de la norme, la STL peut clairement ĂȘtre attribuĂ©e Ă  la bibliothĂšque Containers, Ă  la bibliothĂšque Iterators, Ă  la bibliothĂšque Algorithm et partiellement Ă  la bibliothĂšque General Utilities.

Dans les descriptions de travail, vous pouvez souvent trouver 2 exigences distinctes - la connaissance de C ++ et la familiarité avec STL . Je n'ai jamais compris cela, car STL fait partie intégrante du langage depuis la premiÚre norme de 1998.

Lire la suite: une fois (russe) , deux (anglais)

UB


Comportement indéfini. Comportement indéfini. Ce comportement est dans les cas d'erreur pour lesquels la norme n'a pas d'exigences. Beaucoup d'entre eux sont explicitement répertoriés dans la norme comme conduisant à l' UB . Il s'agit, par exemple:

  • violation des limites d'un tableau ou d'un conteneur STL
  • utilisation d'une variable non initialisĂ©e
  • dĂ©rĂ©fĂ©rencer un pointeur nul
  • dĂ©bordement d'entier signĂ©

Le rĂ©sultat d' UB dĂ©pend de tout dans une rangĂ©e - Ă  la fois sur la version du compilateur et sur la mĂ©tĂ©o sur Mars. De plus, ce rĂ©sultat peut ĂȘtre n'importe quoi: une erreur de compilation, une exĂ©cution correcte et un crash. Un comportement indĂ©fini est mauvais, il faut s'en dĂ©barrasser.

Un comportement indĂ©fini, en revanche, ne doit pas ĂȘtre confondu avec un comportement non spĂ©cifiĂ© . Un comportement non spĂ©cifiĂ© est le comportement correct du programme correct, mais qui, avec l'autorisation de la norme, dĂ©pend du compilateur. Et le compilateur n'est pas tenu de le documenter. Par exemple, c'est l'ordre dans lequel les arguments d'une fonction sont Ă©valuĂ©s ou les dĂ©tails d'implĂ©mentation de std::map .

Eh bien, ici, vous pouvez rappeler le comportement défini par l'implémentation. De non spécifié diffÚre dans la disponibilité de la documentation. Exemple: le compilateur est libre de faire le type std::size_t n'importe quelle taille, mais doit indiquer lequel.

En savoir plus: un (russe) , deux (russe) , trois (anglais)

Les subtilités de la langue


ADL


Recherche dĂ©pendante de l'argument. Recherche dĂ©pendante de l'argument. Il est la recherche de Koenig - en l'honneur d'Andrew Koenig. Il s'agit d'un ensemble de rĂšgles pour rĂ©soudre les noms de fonction non qualifiĂ©s (c'est-Ă -dire les noms sans l'opĂ©rateur :: :), en plus de la rĂ©solution de nom habituelle. Autrement dit: le nom d'une fonction est recherchĂ© dans les espaces de noms liĂ©s Ă  ses arguments (c'est l'espace contenant le type de l'argument, le type lui-mĂȘme, s'il s'agit d'une classe, tous ses ancĂȘtres, etc.).

Exemple le plus simple
 #include <iostream> namespace N { struct S {}; void f(S) { std::cout << "f(S)" << std::endl; }; } int main() { N::S s; f(s); } 

La fonction f trouve dans l'espace de noms N uniquement parce que son argument appartient Ă  cet espace.

MĂȘme le trivial std::cout << "Hello World!\n" utilise ADL , std::basic_stream::operator<< pas surchargĂ© pour const char* . Mais le premier argument de cette instruction est std::basic_stream , et le compilateur recherche et trouve une surcharge appropriĂ©e dans l' std .

Quelques détails: ADL n'est pas applicable si une recherche réguliÚre a trouvé une déclaration d'un membre de classe, ou une déclaration de fonction dans le bloc courant sans using , ou une déclaration de non fonction ou modÚle de fonction. Ou si le nom de la fonction est indiqué entre parenthÚses (l'exemple ci-dessus ne compile pas avec (f)(s) ; vous devrez écrire (N::f)(s); ).

Parfois, ADL vous oblige Ă  utiliser des noms de fonction pleinement qualifiĂ©s lĂ  oĂč cela semble inutile.

Par exemple, ce code ne compile pas
 namespace N1 { struct S {}; void foo(S) {}; } namespace N2 { void foo(N1::S) {}; void bar(N1::S s) { foo(s); } } 


En savoir plus: un (anglais) , deux (anglais) , trois (anglais)

CRTP


ModÚle de modÚle curieusement récurrent. ModÚle récursif étrange. L'essence du modÚle est la suivante:

  • une classe hĂ©rite de la classe modĂšle
  • la classe descendante est utilisĂ©e comme paramĂštre modĂšle de sa classe de base

Il est plus facile de donner un exemple:

 template <class T> struct Base {}; struct Derived : Base<Derived> {}; 

Le CRTP est un excellent exemple de polymorphisme statique. La classe de base fournit une interface, les classes dérivées fournissent une implémentation. Mais contrairement au polymorphisme ordinaire, il n'y a pas de surcharge pour créer et utiliser une table de fonctions virtuelles.

Exemple
 template <typename T> struct Base { void action() const { static_cast<T*>(this)->actionImpl(); } }; struct Derived : Base<Derived> { void actionImpl() const { ... } }; template <class Arg> void staticPolymorphicHandler(const Arg& arg) { arg.action(); } 

Lorsqu'il est utilisé correctement, T toujours un descendant de Base , donc static_cast suffit pour static_cast . Oui, dans ce cas, la classe de base connaßt l'interface descendante.

Un autre domaine d'utilisation courant pour CRTP est l'extension (ou le rĂ©trĂ©cissement) de la fonctionnalitĂ© des classes hĂ©ritĂ©es (quelque chose appelĂ© mixin dans certaines langues). Peut-ĂȘtre les exemples les plus cĂ©lĂšbres:

  • struct Derived : singleton<Derived> { 
 }
  • struct Derived : private boost::noncopyable<Derived> { 
 }
  • struct Derived : std::enable_shared_from_this<Derived> { 
 }
  • struct Derived : counter<Derived> { 
 } - compte le nombre d'objets crĂ©Ă©s et / ou existants

Inconvénients, ou plutÎt, moments nécessitant une attention:

  • Il n'y a pas de classe de base commune, vous ne pouvez pas crĂ©er une collection de diffĂ©rents descendants et y accĂ©der via un pointeur sur le type de base. Mais si vous le souhaitez, vous pouvez hĂ©riter Base du type polymorphe habituel.
  • Il existe une opportunitĂ© supplĂ©mentaire de tirer votre pied par nĂ©gligence:

    Exemple
     template <typename T> struct Base {}; struct Derived1 : Base<Derived1> {}; struct Derived2 : Base<Derived1> {}; 

    Mais vous pouvez ajouter une protection:

     private: Base() = default; friend T; 
  • Parce que Puisque toutes les mĂ©thodes ne sont pas virtuelles, les mĂ©thodes du descendant masquent les mĂ©thodes de la classe de base avec les mĂȘmes noms. Par consĂ©quent, il est prĂ©fĂ©rable de les appeler diffĂ©remment.
  • En gĂ©nĂ©ral, les descendants ont des mĂ©thodes publiques qui ne doivent ĂȘtre utilisĂ©es nulle part sauf la classe de base. Ce n'est pas bon, mais cela est corrigĂ© par un niveau d'abstraction supplĂ©mentaire (voir FTSE ).


Lire la suite: une fois (russe) , deux (anglais)

CTAD


Déduction d'argument de modÚle de classe. Déduire automatiquement le type du paramÚtre de modÚle de classe. Il s'agit d'une nouvelle fonctionnalité de C ++ 17. Auparavant, seuls les types de variables ( auto ) et les paramÚtres de modÚle de fonction étaient automatiquement affichés, c'est pourquoi des fonctions auxiliaires comme std::make_pair , std::make_tuple , etc. std::make_tuple . Maintenant, pour la plupart, elles ne sont pas nécessaires, car le compilateur capable d'afficher automatiquement les paramÚtres des modÚles de classe:

 std::pair p{1, 2.0}; // -> std::pair<int, double> auto lck = std::lock_guard{mtx}; // -> std::lock_guard<std::mutex> 

La CTAD est une nouvelle opportunité, elle doit encore évoluer et se développer (C ++ 20 promet déjà des améliorations). En attendant, les restrictions sont les suivantes:

  • L'infĂ©rence partielle des types de paramĂštres n'est pas prise en charge
     std::pair<double> p{1, 2}; //  std::tuple<> t{1, 2, 3}; //  
  • Alias ​​de modĂšle non pris en charge
     template <class T, class U> using MyPair = std::pair<T, U>; MyPair p{1, 2}; //  
  • Les constructeurs disponibles uniquement dans les spĂ©cialisations de modĂšles ne sont pas pris en charge.
     template <class T> struct Wrapper {}; template <> struct Wrapper<int> { Wrapper(int) {}; }; Wrapper w{5}; //  
  • Les modĂšles imbriquĂ©s ne sont pas pris en charge
     template <class T> struct Foo { template <class U> struct Bar { Bar(T, U) {}; }; }; Foo::Bar x{ 1, 2.0 }; //  Foo<int>::Bar x{1, 2.0}; // OK 
  • Évidemment, CTAD ne fonctionnera pas si le type du paramĂštre de modĂšle n'est pas liĂ© aux arguments du constructeur.
     template <class T> struct Collection { Collection(std::size_t size) {}; }; Collection c{5}; //  

Dans certains cas, des rĂšgles d'infĂ©rence explicites qui devraient ĂȘtre dĂ©clarĂ©es dans le mĂȘme bloc que le modĂšle de classe seront utiles.

Exemple
 template <class T> struct Collection { template <class It> Collection(It from, It to) {}; }; Collection c{v.begin(), v.end()}; //  template <class It> Collection(It, It)->Collection<typename std::iterator_traits<It>::value_type>; Collection c{v.begin(), v.end()}; //  OK 


Lire la suite: une fois (russe) , deux (anglais)

EBO


Optimisation de la base vide. Optimisation d'une classe de base vide. Aussi appelé EBCO (Empty Base Class Optimization).

Comme vous le savez, en C ++, la taille d'un objet d'une classe ne peut pas ĂȘtre nulle. Sinon, toute l'arithmĂ©tique des pointeurs se brisera, car Ă  une adresse, il sera possible de marquer autant d'objets diffĂ©rents que vous le souhaitez. Par consĂ©quent, mĂȘme les objets de classes vides (c'est-Ă -dire les classes sans un seul champ non statique) ont une taille non nulle, qui dĂ©pend du compilateur et du systĂšme d'exploitation et est gĂ©nĂ©ralement Ă©gale Ă  1.

Ainsi, la mémoire est gaspillée en vain sur tous les objets des classes vides. Mais pas les objets de leurs descendants, car dans ce cas le Standard fait explicitement une exception. Le compilateur est autorisé à ne pas allouer de mémoire pour une classe de base vide et à enregistrer ainsi non seulement 1 octet de la classe vide, mais les 4 (selon la plate-forme), car il y a également alignement.

Exemple
 struct Empty {}; struct Foo : Empty { int i; }; std::cout << sizeof(Empty) << std::endl; // 1 std::cout << sizeof(Foo) << std::endl; // 4 std::cout << sizeof(int) << std::endl; // 4 

Mais comme diffĂ©rents objets du mĂȘme type ne peuvent pas ĂȘtre placĂ©s Ă  la mĂȘme adresse, l' EBO ne fonctionnera pas si:

  • Une classe vide se trouve deux fois parmi les ancĂȘtres
     struct Empty {}; struct Empty2 : Empty {}; struct Foo : Empty, Empty2 { int i; }; std::cout << sizeof(Empty) << std::endl; // 1 std::cout << sizeof(Empty2) << std::endl; // 1 std::cout << sizeof(Foo) << std::endl; // 8 
  • Le premier champ non statique est un objet de la mĂȘme classe vide ou son descendant
     struct Empty {}; struct Foo : Empty { Empty e; int i; }; std::cout << sizeof(Empty) << std::endl; // 1 std::cout << sizeof(Foo) << std::endl; // 8 

Dans les cas oĂč les objets des classes vides sont des champs non statiques, aucune optimisation n'est fournie (pour l'instant, l'attribut [[no_unique_address]] apparaĂźtra en C ++ 20). Mais dĂ©penser 4 octets (ou combien le compilateur a besoin) pour chacun de ces champs est une honte, vous pouvez donc «rĂ©duire» les objets de classes vides avec le premier champ non statique non vide par vous-mĂȘme.

Exemple
 struct Empty1 {}; struct Empty2 {}; template <class Member, class ... Empty> struct EmptyOptimization : Empty ... { Member member; }; struct Foo { EmptyOptimization<int, Empty1, Empty2> data; }; 

Étrange, mais dans ce cas, la taille de Foo est diffĂ©rente pour diffĂ©rents compilateurs, pour MSVC 2019, elle est de 8, pour GCC 8.3.0, elle est de 4. Mais dans tous les cas, l'augmentation du nombre de classes vides n'affecte pas la taille de Foo .

En savoir plus: une fois (anglais) , deux (anglais)

IIFE


Expression de fonction immĂ©diatement invoquĂ©e. Expression fonctionnelle appelĂ©e immĂ©diatement. En gĂ©nĂ©ral, c'est un idiome en JavaScript, d'oĂč Jason Turner l'a empruntĂ© avec le nom. En fait, il s'agit simplement de crĂ©er et d'appeler immĂ©diatement un lambda:

 const auto myVar = [&] { if (condition1()) { return computeSomeComplexStuff(); } return condition2() ? computeSonethingElse() : DEFAULT_VALUE; } (); 

Pourquoi est-ce nécessaire? Eh bien, par exemple, comme dans le code ci-dessus afin d'initialiser une constante par le résultat d'un calcul non trivial et de ne pas obstruer la portée avec des variables et des fonctions inutiles.

En savoir plus: une fois (anglais) , deux (anglais)

NVI


Interface non virtuelle. Interface non virtuelle. Selon cet idiome, une interface de classe ouverte ne devrait pas contenir de fonctions virtuelles. Toutes les fonctions virtuelles sont rendues privées (protégées au maximum) et appelées à l'intérieur non ouvertes virtuelles.

Exemple
 class Base { public: virtual ~Base() = default; void foo() { // check precondition fooImpl(); // check postconditions } private: virtual void fooImpl() = 0; }; class Derived : public Base { private: void fooImpl() override { } }; 

Pourquoi est-ce nécessaire:

  • Chaque fonction virtuelle ouverte fait 2 choses: elle dĂ©finit l'interface publique de la classe et participe au comportement prioritaire dans les classes descendantes. L'utilisation de NVI Ă©limine de telles fonctions avec une double charge: l'interface est dĂ©finie par certaines fonctions, le changement de comportement par d'autres. Vous pouvez les modifier tous les deux indĂ©pendamment l'un de l'autre.
  • (- -, . .), (. DRY ) — — . C'est-Ă -dire .

NVI – , (- ) (. FBC ).

: (.) , (.)

RVO NRVO


(Named) Return Value Optimization. () . copy elision – , . , ( copy elision – ).

 Foo bar() { return Foo(); } int main() { auto f = bar(); } 

RVO Foo bar , main ( bar ), f . RVO , bar f .

: main f . bar ( ), , .

NRVO RVO , , return , .

 Foo bar() { Foo result; return result; } 

, NRVO , . , , , — NRVO .

NRVO
 Foo bar(bool condition) { if (condition) { Foo f1; return f1; } Foo f2; return f2; } 

RVO . NRVO .

RVO NRVO – . , . C++17: RVO copy elision, , .

: (N)RVO — . C++14 , C++17 RVO , C++20 – .

. -, (N)RVO - , .. . -, result std::move(result) , NRVO . : RVO prvalue, NRVO – lvalue, a std::move(result) – xvalue.

: (.) , (.) , (.)

SFINAE


Substitution Failure Is Not An Error. — . SFINAE — — — ++. , , , . , :

  1. — (. ADL ).
  2. — , , , . .
  3. (viable functions), . — .

SFINAE : , , , ( ). .

SFINAE , , . - , . . , .

 #include <iostream> #include <type_traits> #include <utility> template <class, class = void> struct HasToString : std::false_type {}; //    ,      //   -    ,  //     —  ,    ,   template <class T> struct HasToString<T, std::void_t<decltype(&T::toString)>> : std::is_same<std::string, decltype(std::declval<T>().toString())> {}; struct Foo { std::string toString() { return {}; } }; int main() { std::cout << HasToString<Foo>::value << std::endl; // 1 std::cout << HasToString<int>::value << std::endl; // 0 } 

C++17 static if SFINAE , C++20 . .

: (.) , (.) , (.)

SBO, SOO, SSO


Small Buffer/Object/String Optimization. //. SSO Small Size Optimization, , , SSO – . SBO SOO – , SSO – .

, , - . , . , ( ), .

, std::string :

 class string { char* begin_; size_t size_; size_t capacity_; }; 

24 ( ). C'est-à-dire 24 . 24, , . . - . 8 ( — 24 ):

 class string { union Buffer { char* begin_; char local_[8]; }; Buffer buffer_; size_t _size; size_t _capacity; }; 

, — . .

std::string SSO std::function . std::vector , . . , std::swap , . SBO ( std::string ). boost::container::small_vector , , SBO .

: (.) , (.)

UDPATE


PyerK .

CV


const volatile. const , / , , UB . volatile , / (, - - ), . volatile volatile UB .

: (.) , (.) , (.)

LTO


Link Time Optimization. . , , . . . , : , . , .

: (.)

PCH


Precompiled Headers. . , . , .

: (.)

PGO


Profile-Guided Optimization. . , , . , .

: (.)

SEH/VEH


Structured/Vectored Exception Handling. MSVC . try-catch SEH : __try , __except , __finally , , , , - , . . VEH , .

: (.)

TMP


Template Meta-Programming. . — . C++ . . , TMP C++ , . . .

: (.)

VLA


Variable-Length Arrays. . C'est-Ă -dire , :
 void foo(int n) { int array[n]; } 

C++ . , . . C C99. C++ .

: (.)

PS


- - — . , , , C++. , , .

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


All Articles