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âą
UBSubtilitĂ©s de la langue:âą
ADLâą
CRTPâą
CTADâą
EBOâą
IIFEâą
NVIâą
RVO et NRVOâą
SFINAEâą
SBO, SOO, SSOMISE Ă JOUR:âą
CVâą
LTOâą
PCHâą
PGOâą
SEH / VEHâą
TMPâą
VLAChoses 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):
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 {
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:
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};
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};
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 };
Ă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()};
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;
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;
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;
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() {
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 â â â ++. , , , . , :
- â (. ADL ).
- â , , , . .
- (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++. , , .