Fonctionnalités cachées de C ++? [fermé]


114

Aucun amour C ++ quand il s'agit de la ligne de questions «fonctionnalités cachées»? Je pensais que je le jetterais là-bas. Quelles sont certaines des fonctionnalités cachées de C ++?


@Devtron - J'ai vu des bugs impressionnants (c'est-à-dire des comportements inattendus) vendus comme fonctionnalités. En fait, l'industrie des jeux tente de faire en sorte que cela se produise de nos jours et l'appelle "gameplay émergent" (aussi, consultez "TK Surfing" de Psi-Ops, était purement un bug, puis ils l'ont laissé tel quel et c'est l'un des meilleures caractéristiques du jeu IMHO)
Grant Peters

5
@Laith J: Peu de gens ont lu la norme ISO C ++ de 786 pages d'un bout à l'autre - mais je suppose que vous l'avez fait, et vous l'avez tout conservé, n'est-ce pas?
j_random_hacker

2
@Laith, @j_random: Voir ma question "Qu'est-ce qu'une blague de programmeur, comment la reconnaître et quelle est la réponse appropriée" sur stackoverflow.com/questions/1/you-have-been-link-rolled .

Réponses:


308

La plupart des programmeurs C ++ sont familiers avec l'opérateur ternaire:

x = (y < 0) ? 10 : 20;

Cependant, ils ne réalisent pas qu'il peut être utilisé comme une lvalue:

(a == 0 ? a : b) = 1;

qui est un raccourci pour

if (a == 0)
    a = 1;
else
    b = 1;

Utiliser avec précaution :-)


11
Très intéressant. Je peux voir que cela rend un code illisible.
Jason Baker

112
Yikes. (a == 0? a: b) = (y <0? 10: 20);
Jasper Bekkers

52
(b? trueCount: falseCount) ++
Pavel Radzivilovsky

12
J'sais si elle est spécifique du CCG, mais je fus surpris de trouver cela a fonctionné aussi: (value ? function1 : function2)().
Chris Burt-Brown

3
@Chris Burt-Brown: Non, cela devrait fonctionner partout s'ils ont le même type (c'est-à-dire pas d'arguments par défaut) function1et function2sont implicitement convertis en pointeurs de fonction, et le résultat est implicitement reconverti.
MSalters

238

Vous pouvez placer des URI dans la source C ++ sans erreur. Par exemple:

void foo() {
    http://stackoverflow.com/
    int bar = 4;

    ...
}

41
Mais un seul par fonction, je suppose? :)
Constantin

51
@jpoh: http suivi de deux points devient un "label" que vous utiliserez plus tard dans une instruction goto. vous obtenez cet avertissement de votre compilateur car il n'est utilisé dans aucune instruction goto de l'exemple ci-dessus.
utku_karatas

9
Vous pouvez en ajouter plusieurs à condition qu'ils aient des protocoles différents! ftp.microsoft.com gopher: //aerv.nl/1 et ainsi de suite ...
Daniel Earwicker

4
@Pavel: Un identifiant suivi de deux points est une étiquette (à utiliser avec goto, ce que C ++ a). Tout ce qui suit deux barres obliques est un commentaire. Par conséquent, avec http://stackoverflow.com, httpest une étiquette (vous pourriez théoriquement écrire goto http;), et //stackoverflow.comn'est qu'un commentaire de fin de ligne. Les deux sont du C ++ légal, donc la construction se compile. Cela ne fait rien de vaguement utile, bien sûr.
David Thornley

8
Malheureusement, goto http;ne suit pas réellement l'URL. :(
Yakov Galka

140

Arithmétique de pointeur.

Les programmeurs C ++ préfèrent éviter les pointeurs à cause des bogues qui peuvent être introduits.

Le C ++ le plus cool que j'aie jamais vu? Littéraux analogiques.


11
On évite les pointeurs à cause des bugs? Les pointeurs sont essentiellement tout ce qui concerne le codage C ++ dynamique!
Nick Bedford

1
Les littéraux analogiques sont parfaits pour les entrées de concours C ++ masquées, en particulier le type ASCII-art.
Synetech

119

Je suis d'accord avec la plupart des articles: C ++ est un langage multi-paradigme, donc les fonctionnalités "cachées" que vous trouverez (autres que les "comportements indéfinis" que vous devriez éviter à tout prix) sont des utilisations intelligentes des installations.

La plupart de ces fonctionnalités ne sont pas des fonctionnalités intégrées du langage, mais des fonctionnalités basées sur une bibliothèque.

Le plus important est le RAII , souvent ignoré depuis des années par les développeurs C ++ venant du monde C. La surcharge des opérateurs est souvent une fonctionnalité mal comprise qui permet à la fois un comportement de type tableau (opérateur d'indice), des opérations de type pointeur (pointeurs intelligents) et des opérations de type intégré (multiplication des matrices.

L'utilisation de l' exception est souvent difficile, mais avec un peu de travail, elle peut produire un code vraiment robuste grâce à des spécifications de sécurité d'exception (y compris du code qui n'échouera pas, ou qui aura des fonctionnalités de type commit qui réussiront ou reviendront à son état d'origine).

La fonctionnalité "cachée" la plus connue de C ++ est la métaprogrammation de modèles , car elle vous permet d'exécuter partiellement (ou totalement) votre programme au moment de la compilation au lieu de l'exécution. C'est difficile, cependant, et vous devez avoir une solide maîtrise des modèles avant de l'essayer.

D'autres utilisent le paradigme multiple pour produire des «façons de programmer» en dehors de l'ancêtre de C ++, c'est-à-dire C.

En utilisant des foncteurs , vous pouvez simuler des fonctions, avec la sécurité de type supplémentaire et étant avec état. En utilisant le modèle de commande , vous pouvez retarder l'exécution du code. La plupart des autres modèles de conception peuvent être facilement et efficacement implémentés en C ++ pour produire des styles de codage alternatifs qui ne sont pas censés être dans la liste des «paradigmes officiels du C ++».

En utilisant des modèles , vous pouvez produire du code qui fonctionnera sur la plupart des types, y compris celui que vous avez pensé au début. Vous pouvez également augmenter la sécurité des types (comme un malloc / realloc / free). Les fonctionnalités des objets C ++ sont vraiment puissantes (et donc dangereuses si elles sont utilisées avec négligence), mais même le polymorphisme dynamique a sa version statique en C ++: le CRTP .

J'ai trouvé que la plupart des livres de type " C ++ efficace " de Scott Meyers ou des livres de type " C ++ exceptionnel " de Herb Sutter sont à la fois faciles à lire et sont assez riches d'informations sur les fonctionnalités connues et moins connues du C ++.

Parmi mes préférés, il y en a un qui devrait faire sortir les cheveux de tout programmeur Java de l'horreur: en C ++, le moyen le plus orienté objet d'ajouter une fonctionnalité à un objet est via une fonction non-membre non-amie, au lieu d'un membre- fonction (c'est-à-dire méthode de classe), car:

  • En C ++, l'interface d'une classe est à la fois ses fonctions membres et les fonctions non membres dans le même espace de noms

  • les fonctions non-amies non-membres n'ont pas d'accès privilégié à la classe interne. En tant que tel, l'utilisation d'une fonction membre sur une fonction non-amie non-membre affaiblira l'encapsulation de la classe.

Cela ne manque jamais de surprendre même les développeurs expérimentés.

(Source: Entre autres, le gourou en ligne de Herb Sutter # 84: http://www.gotw.ca/gotw/084.htm )


+1 réponse très approfondie. c'est incomplet pour des raisons évidentes (sinon il n'y aurait plus de "fonctionnalités cachées"!): p au premier point à la fin de la réponse, vous avez mentionné les membres d'une interface de classe. voulez-vous dire ".. est à la fois ses fonctions membres et les fonctions ami non membre"?
wilhelmtell


ce que vous mentionnez avec 1 doit être la recherche de Koenig, non?
Özgür

1
@wilhelmtell: Non non non ... :-p ... Je veux dire "ses fonctions membres et ses fonctions non-membres NON AMI" .... Koenig's Lookup s'assurera que ces fonctions seront considérées plus tôt que les autres " dehors "fonctionne dans sa recherche de symboles
paercebal

7
Excellent post, et +1 en particulier pour la dernière partie, ce que trop peu de gens réalisent. J'ajouterais probablement aussi la bibliothèque Boost en tant que "fonctionnalité cachée". Je la considère à peu près comme la bibliothèque standard que C ++ aurait dû avoir. ;)
jalf

118

Une caractéristique du langage que je considère comme quelque peu cachée, car je n'en avais jamais entendu parler pendant tout mon séjour à l'école, est l'alias d'espace de noms. Cela n'a pas été porté à mon attention jusqu'à ce que j'en trouve des exemples dans la documentation du boost. Bien sûr, maintenant que je le connais, vous pouvez le trouver dans n'importe quelle référence C ++ standard.

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );

1
Je suppose que c'est utile si vous ne voulez pas utiliser using.
Siqi Lin

4
C'est également utile comme moyen de basculer entre les implémentations, que ce soit en sélectionnant, disons thread-safe ou non thread-safe, ou version 1 contre 2.
Tony Delroy

3
C'est particulièrement utile si vous travaillez sur un très grand projet avec de grandes hiérarchies d'espaces de noms et que vous ne voulez pas que vos en-têtes causent une pollution de l'espace de noms (et que vous voulez que vos déclarations de variables soient lisibles par l'homme).
Brandon Bohrer

102

Non seulement les variables peuvent être déclarées dans la partie init d'une forboucle, mais aussi les classes et les fonctions.

for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
    ...
}

Cela permet plusieurs variables de types différents.


31
C'est bien de savoir que vous pouvez le faire, mais personnellement, j'essaierais vraiment d'éviter de faire quelque chose comme ça. Surtout parce que c'est difficile à lire.
Zoomulator

2
En fait, ce qui fonctionnerait dans ce contexte est d'utiliser une paire: for (std :: pair <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
Valentin Heinitz

2
@Valentin eh bien, je vous recommande d'essayer de faire un rapport de bogue contre VS2008 au lieu de voter contre la fonctionnalité cachée. C'est clairement la faute de votre compilateur.
Johannes Schaub - litb

2
Hmm, cela ne fonctionne pas non plus dans msvc10. Comment triste :(
avakar

2
@avakar en fait, gcc a introduit un bogue qui le fait rejeter aussi, dans la v4.6 :) voir gcc.gnu.org/bugzilla/show_bug.cgi?id=46791
Johannes Schaub - litb

77

L'opérateur de tableau est associatif.

A [8] est un synonyme de * (A + 8). Puisque l'addition est associative, cela peut être réécrit comme * (8 + A), qui est synonyme de ..... 8 [A]

Vous n'avez pas dit utile ... :-)


15
En fait, lorsque vous utilisez cette astuce, vous devez vraiment faire attention au type que vous utilisez. A [8] est en fait le 8e A tandis que 8 [A] est l'entier Ath commençant à l'adresse 8. Si A est un octet, vous avez un bogue.
Vincent Robert

38
vous voulez dire «commutatif» où vous dites «associatif»?
DarenW

28
Vincent, tu as tort. Le type de An'importe pas du tout. Par exemple, si Aétait a char*, le code serait toujours valide.
Konrad Rudolph le

11
Attention, A doit être un pointeur et non un opérateur de surcharge de classe [].
David Rodríguez - dribeas le

15
Vincent, en cela il doit y avoir un type intégral et un type pointeur, et ni C ni C ++ ne se soucient de savoir lequel passe en premier.
David Thornley

73

Une chose que l'on sait peu est que les unions peuvent aussi être des modèles:

template<typename From, typename To>
union union_cast {
    From from;
    To   to;

    union_cast(From from)
        :from(from) { }

    To getTo() const { return to; }
};

Et ils peuvent aussi avoir des constructeurs et des fonctions membres. Rien qui ait à voir avec l'héritage (y compris les fonctions virtuelles).


Intéressant! Alors, devez-vous initialiser tous les membres? Suit-il l'ordre de structure habituel, ce qui implique que le dernier membre sera initialisé «au-dessus» des membres précédents?
j_random_hacker

j_random_hacker oh, c'est un non-sens. bonne prise. je l'ai écrit comme ce serait une structure. attendez, je vais le réparer
Johannes Schaub - litb

Cela n'invoque-t-il pas un comportement indéfini?
Greg Bacon

7
@gbacon, oui, il invoque un comportement non défini si Fromet Tosont définis et utilisés en conséquence. Une telle union peut être utilisée avec un comportement défini ( Toétant un tableau de caractères non signés ou une structure partageant une séquence initiale avec From). Même si vous l'utilisez d'une manière non définie, cela peut toujours être utile pour les travaux de bas niveau. Quoi qu'il en soit, ce n'est qu'un exemple d'un modèle d'union - il peut y avoir d'autres utilisations pour une union basée sur un modèle.
Johannes Schaub - litb

3
Attention au constructeur. Notez que vous devez uniquement construire le premier élément et que ce n'est autorisé que dans C ++ 0x. À partir de la norme actuelle, vous devez vous en tenir à des types trivialement constructibles. Et pas de destructeurs.
Potatoswatter

72

C ++ est un standard, il ne devrait pas y avoir de fonctionnalités cachées ...

C ++ est un langage multi-paradigme, vous pouvez parier votre dernier argent sur l'existence de fonctionnalités cachées. Un exemple parmi tant d'autres: la métaprogrammation de modèles . Personne dans le comité de normalisation n'avait l'intention qu'il y ait un sous-langage complet de Turing qui soit exécuté au moment de la compilation.


65

Une autre fonctionnalité cachée qui ne fonctionne pas en C est la fonctionnalité de l' +opérateur unaire . Vous pouvez l'utiliser pour promouvoir et décomposer toutes sortes de choses

Conversion d'une énumération en un entier

+AnEnumeratorValue

Et votre valeur d'énumération qui avait auparavant son type d'énumération a maintenant le type entier parfait qui peut correspondre à sa valeur. Manuellement, vous ne sauriez guère ce type! Cela est nécessaire, par exemple, lorsque vous souhaitez implémenter un opérateur surchargé pour votre énumération.

Obtenir la valeur d'une variable

Vous devez utiliser une classe qui utilise un initialiseur statique en classe sans définition hors classe, mais parfois il échoue à se lier? L'opérateur peut aider à créer un temporaire sans faire d'hypothèses ou de dépendances sur son type

struct Foo {
  static int const value = 42;
};

// This does something interesting...
template<typename T>
void f(T const&);

int main() {
  // fails to link - tries to get the address of "Foo::value"!
  f(Foo::value);

  // works - pass a temporary value
  f(+Foo::value);
}

Décomposition d'un tableau en pointeur

Voulez-vous passer deux pointeurs à une fonction, mais cela ne fonctionnera tout simplement pas? L'opérateur peut aider

// This does something interesting...
template<typename T>
void f(T const& a, T const& b);

int main() {
  int a[2];
  int b[3];
  f(a, b); // won't work! different values for "T"!
  f(+a, +b); // works! T is "int*" both time
}

61

La durée de vie des temporaires liées aux références const est celle que peu de gens connaissent. Ou du moins, c'est ma connaissance C ++ préférée que la plupart des gens ne connaissent pas.

const MyClass& x = MyClass(); // temporary exists as long as x is in scope

3
Peux-tu élaborer? Comme c'est juste que vous taquinez;)
Joseph Garvin

8
ScopeGuard ( ddj.com/cpp/184403758 ) est un excellent exemple qui exploite cette fonctionnalité.
MSN

2
Je suis avec Joseph Garvin. Veuillez nous éclairer.
Peter Mortensen

Je viens de le faire dans les commentaires. De plus, c'est une conséquence naturelle de l'utilisation d'un paramètre de référence const.
MSN


52

Une fonctionnalité intéressante qui n'est pas souvent utilisée est le bloc try-catch à l'échelle de la fonction:

int Function()
try
{
   // do something here
   return 42;
}
catch(...)
{
   return -1;
}

L'utilisation principale serait de traduire l'exception en une autre classe d'exception et de la renvoyer, ou de traduire entre les exceptions et la gestion des codes d'erreur basée sur les retours.


Je ne pense pas que vous puissiez à returnpartir du bloc catch de Function Try, mais uniquement de nouveau.
Constantin

J'ai juste essayé de compiler ce qui précède, et cela n'a donné aucun avertissement. Je pense que l'exemple ci-dessus fonctionne.
vividos

7
le retour n'est interdit qu'aux constructeurs. Le bloc try de fonction d'un constructeur attrapera les erreurs d'initialisation de la base et des membres (le seul cas où un bloc try de fonction fait quelque chose de différent que d'avoir simplement un essai à l'intérieur de la fonction); ne pas relancer entraînerait un objet incomplet.
puetzk

Oui. Ceci est très utile. J'ai écrit des macros BEGIN_COM_METHOD et END_COM_METHOD pour attraper les exceptions et renvoyer HRESULTS afin que les exceptions ne fuient pas d'une classe implémentant une interface COM. Cela a bien fonctionné.
Scott Langham

3
Comme l'a souligné @puetzk, c'est le seul moyen de gérer les exceptions levées par quoi que ce soit dans la liste d'initialisation d' un constructeur , comme les constructeurs de classes de base ou ceux des membres de données.
anton.burger

44

Beaucoup connaissent la métafonction identity/ id, mais il y a un bon cas d'utilisation pour les cas non-template: Facilité d'écriture des déclarations:

// void (*f)(); // same
id<void()>::type *f;

// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);

// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];

// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;

Cela aide grandement à déchiffrer les déclarations C ++!

// boost::identity is pretty much the same
template<typename T> 
struct id { typedef T type; };

Intéressant, mais au début, j'ai eu plus de mal à lire certaines de ces définitions. Une autre façon de résoudre le problème interne des déclarations C ++ consiste à écrire des alias de type de modèle: template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;-> pointer<function<void,int>> f(pointer<function<void,void>>);or pointer<void(int)> f(pointer<void()>);orfunction<pointer<function<void,int>>,pointer<function<void,void>>> f;
bames53

42

Une fonctionnalité assez cachée est que vous pouvez définir des variables dans une condition if, et sa portée ne s'étendra que sur les blocs if et else:

if(int * p = getPointer()) {
    // do something
}

Certaines macros l'utilisent, par exemple pour fournir une portée "verrouillée" comme celle-ci:

struct MutexLocker { 
    MutexLocker(Mutex&);
    ~MutexLocker(); 
    operator bool() const { return false; } 
private:
    Mutex &m;
};

#define locked(mutex) if(MutexLocker const& lock = MutexLocker(mutex)) {} else 

void someCriticalPath() {
    locked(myLocker) { /* ... */ }
}

BOOST_FOREACH l'utilise également sous le capot. Pour compléter cela, ce n'est pas seulement possible dans un if, mais aussi dans un switch:

switch(int value = getIt()) {
    // ...
}

et dans une boucle while:

while(SomeThing t = getSomeThing()) {
    // ...
}

(et aussi en condition pour). Mais je ne sais pas trop si tout cela est utile :)


Soigné! Je n'ai jamais su que vous pouviez faire cela ... cela aurait (et va) économiser des tracas lors de l'écriture de code avec des valeurs de retour d'erreur. Existe-t-il un moyen d'avoir toujours un conditionnel au lieu de juste! = 0 sous cette forme? if ((int r = func ()) <0) ne semble pas fonctionner ...
puetzk

puetzk, non il n'y en a pas. mais heureux que vous l'
aimiez

4
@Frerich, ce n'est pas du tout possible en code C. Je pense que vous pensez if((a = f()) == b) ..., mais cette réponse déclare en fait une variable dans la condition.
Johannes Schaub - litb

1
@Angry c'est très différent, car la déclaration de variable est testée immédiatement pour sa valeur booléenne. Il y a aussi un mappage vers les boucles for, qui ressemble à for(...; int i = foo(); ) ...;Cela traversera le corps aussi longtemps que ic'est vrai, en l'initialisant à chaque fois. La boucle que vous montrez montre simplement une déclaration de variable, mais pas une déclaration de variable qui agit simultanément comme une condition :)
Johannes Schaub - litb

5
Très bien, sauf que vous n'avez pas mentionné l'utilisation prévue pour cette fonctionnalité était pour les lancers de pointeurs dynamiques, je crois.
mmocny

29

Empêcher l'opérateur de virgule d'appeler les surcharges d'opérateur

Parfois, vous utilisez correctement l'opérateur virgule, mais vous voulez vous assurer qu'aucun opérateur virgule défini par l'utilisateur ne vous gêne, car par exemple, vous vous fiez aux points de séquence entre le côté gauche et droit ou vous voulez vous assurer que rien n'interfère avec le action. C'est là void()qu'entre en jeu:

for(T i, j; can_continue(i, j); ++i, void(), ++j)
  do_code(i, j);

Ignorez les espaces réservés que j'ai mis pour la condition et le code. L'important est le void(), qui oblige le compilateur à utiliser l'opérateur virgule intégré. Cela peut être utile lors de l'implémentation de classes de traits, parfois aussi.


Je viens de l'utiliser pour terminer mon ignorant d'expression exagérée . :)
GManNickG

28

Initialisation du tableau dans le constructeur. Par exemple dans une classe si nous avons un tableau de intas:

class clName
{
  clName();
  int a[10];
};

Nous pouvons initialiser tous les éléments du tableau à sa valeur par défaut (ici tous les éléments du tableau à zéro) dans le constructeur comme:

clName::clName() : a()
{
}

6
Vous pouvez le faire avec n'importe quel tableau n'importe où.
Potatoswatter

@Potatoswatter: plus difficile qu'il n'y paraît, en raison de l'analyse la plus vexante. Je ne peux penser à aucun autre endroit où cela puisse être fait, sauf peut - être une valeur de retour
Mooing Duck

Si le type du tableau est un type de classe, alors ce n'est pas nécessaire, non?
Thomas Eding

27

Oooh, je peux plutôt proposer une liste de détestations pour les animaux:

  • Les destructeurs doivent être virtuels si vous avez l'intention de les utiliser de manière polymorphe
  • Parfois, les membres sont initialisés par défaut, parfois ils ne le sont pas
  • Les classes locales ne peuvent pas être utilisées comme paramètres de modèle (les rend moins utiles)
  • spécificateurs d'exception: semblent utiles, mais ne le sont pas
  • les surcharges de fonctions masquent les fonctions de classe de base avec des signatures différentes.
  • pas de standardisation utile sur l'internationalisation (jeu de caractères large standard portable, n'importe qui? Nous devrons attendre jusqu'à C ++ 0x)

Du coté positif

  • fonctionnalité cachée: blocs d'essais de fonction. Malheureusement, je n'ai pas trouvé d'utilisation pour cela. Oui je sais pourquoi ils l'ont ajouté, mais vous devez relancer un constructeur, ce qui le rend inutile.
  • Il vaut la peine d'examiner attentivement les garanties de la STL sur la validité de l'itérateur après la modification du conteneur, ce qui peut vous permettre de créer des boucles légèrement plus agréables.
  • Boost - ce n'est pas un secret mais cela vaut la peine d'être utilisé.
  • Optimisation de la valeur de retour (pas évidente, mais elle est spécifiquement autorisée par la norme)
  • Functors aka objets de fonction aka operator (). Ceci est largement utilisé par la STL. ce n'est pas vraiment un secret, mais c'est un effet secondaire astucieux de la surcharge des opérateurs et des modèles.

16
pet hate: pas d'ABI défini pour les applications C ++, contrairement aux applications C que tout le monde utilise car chaque langage peut garantir d'appeler une fonction C, personne ne peut faire de même pour C ++.
gbjbaanb

8
Les destructeurs ne doivent être virtuels que si vous avez l'intention de détruire de manière polymorphe, ce qui est légèrement différent du premier point.
David Rodríguez - dribeas le

2
Avec C ++ 0x, les types locaux peuvent être utilisés comme paramètres de modèle.
tstenner

1
Avec C ++ 0x, les destructeurs seront virtuels si l'objet a des fonctions virtuelles (c'est-à-dire une vtable).
Macke

n'oubliez pas NRVO, et bien sûr toute optimisation est autorisée tant qu'elle ne change pas la sortie du programme
jk.

26

Vous pouvez accéder aux données protégées et aux membres de fonction de n'importe quelle classe, sans comportement indéfini et avec la sémantique attendue. Lisez la suite pour voir comment. Lisez également le rapport de défaut à ce sujet.

Normalement, C ++ vous interdit d'accéder aux membres protégés non statiques de l'objet d'une classe, même si cette classe est votre classe de base

struct A {
protected:
    int a;
};

struct B : A {
    // error: can't access protected member
    static int get(A &x) { return x.a; }
};

struct C : A { };

C'est interdit: vous et le compilateur ne savez pas sur quoi la référence pointe réellement. Il peut s'agir d'un Cobjet, auquel cas la classe Bn'a aucune activité et aucune idée de ses données. Un tel accès n'est accordé que s'il xs'agit d'une référence à une classe dérivée ou à une classe dérivée de celle-ci. Et cela pourrait permettre à un morceau de code arbitraire de lire n'importe quel membre protégé en créant simplement une classe "à jeter" qui lit les membres, par exemple std::stack:

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            // error: stack<int>::c is protected
            return s.c;
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

Comme vous le voyez, cela causerait bien trop de dégâts. Mais maintenant, les pointeurs membres permettent de contourner cette protection! Le point clé est que le type d'un pointeur de membre est lié à la classe qui contient réellement ledit membre - pas à la classe que vous avez spécifiée lors de la prise de l'adresse. Cela nous permet de contourner la vérification

struct A {
protected:
    int a;
};

struct B : A {
    // valid: *can* access protected member
    static int get(A &x) { return x.*(&B::a); }
};

struct C : A { };

Et bien sûr, cela fonctionne également avec l' std::stackexemple.

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            return s.*(pillager::c);
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

Cela sera encore plus facile avec une déclaration using dans la classe dérivée, qui rend le nom du membre public et fait référence au membre de la classe de base.

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        using std::stack<int>::c;
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = s.*(&pillager::c);
}


26

Une autre fonctionnalité cachée est que vous pouvez appeler des objets de classe qui peuvent être convertis en pointeurs de fonction ou en références. La résolution des surcharges se fait sur leur résultat et les arguments sont parfaitement transmis.

template<typename Func1, typename Func2>
class callable {
  Func1 *m_f1;
  Func2 *m_f2;

public:
  callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
  operator Func1*() { return m_f1; }
  operator Func2*() { return m_f2; }
};

void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }

int main() {
  callable<void(int), void(long)> c(foo, bar);
  c(42); // calls foo
  c(42L); // calls bar
}

Celles-ci sont appelées "fonctions d'appel de substitution".


1
Lorsque vous dites que la résolution de surcharge est effectuée sur le résultat de ceux-ci, voulez-vous dire qu'elle la convertit réellement en deux Functors, puis qu'elle surcharge la résolution? J'ai essayé d'imprimer quelque chose dans l'opérateur Func1 * () et l'opérateur Func2 * (), mais il semble choisir le bon quand il détermine quel opérateur de conversion appeler.
navigateur le

3
@navigator, oui, il se convertit conceptuellement en deux et choisit ensuite le meilleur. Il n'a pas besoin de les appeler, car il sait déjà à partir du type de résultat ce qu'ils donneront. L'appel réel est fait quand il s'avère ce qui a finalement été choisi.
Johannes Schaub - litb

26

Fonctionnalités cachées:

  1. Les fonctions virtuelles pures peuvent avoir une implémentation. Exemple courant, pur destructeur virtuel.
  2. Si une fonction lève une exception non répertoriée dans ses spécifications d'exception, mais que la fonction l'a std::bad_exceptiondans sa spécification d'exception, l'exception est convertie std::bad_exceptionet levée automatiquement. De cette façon, vous saurez au moins qu'un a bad_exceptionété lancé. En savoir plus ici .

  3. blocs d'essai de fonction

  4. Le mot-clé template pour lever l'ambiguïté des typedefs dans un modèle de classe. Si le nom d'une spécialisation de modèle de membre apparaît après ., ->ou ::opérateur, et ce nom a des paramètres de modèle explicitement qualifiés, préfixe le nom du modèle de membre avec le modèle mot - clé. En savoir plus ici .

  5. les valeurs par défaut des paramètres de fonction peuvent être modifiées lors de l'exécution. En savoir plus ici .

  6. A[i] fonctionne aussi bien que i[A]

  7. Les instances temporaires d'une classe peuvent être modifiées! Une fonction membre non-const peut être appelée sur un objet temporaire. Par exemple:

    struct Bar {
      void modify() {}
    }
    int main (void) {
      Bar().modify();   /* non-const function invoked on a temporary. */
    }

    En savoir plus ici .

  8. Si deux types différents sont présents avant et après le :dans l' ?:expression de l'opérateur ternary ( ), le type résultant de l'expression est celui qui est le plus général des deux. Par exemple:

    void foo (int) {}
    void foo (double) {}
    struct X {
      X (double d = 0.0) {}
    };
    void foo (X) {} 
    
    int main(void) {
      int i = 1;
      foo(i ? 0 : 0.0); // calls foo(double)
      X x;
      foo(i ? 0.0 : x);  // calls foo(X)
    }

P Daddy: A [i] == * (A + i) == * (i + A) == i [A]
abelenky

J'obtiens la commutation, c'est juste que cela signifie que [] n'a pas de valeur sémantique propre et équivaut simplement à un remplacement de style macro où "x [y]" est remplacé par "(* ((x) + (y ))) ". Pas du tout ce à quoi je m'attendais. Je me demande pourquoi c'est défini de cette façon.
P Daddy

Compatibilité descendante avec C
jmucchiello

2
Concernant votre premier point: il existe un cas particulier où vous devez implémenter une fonction virtuelle pure: des destructeurs virtuels purs.
Frerich Raabe

24

map::operator[]crée une entrée si la clé est manquante et renvoie une référence à la valeur d'entrée construite par défaut. Vous pouvez donc écrire:

map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
  s.assign(...);
}
cout << s;

Je suis étonné de voir combien de programmeurs C ++ ne le savent pas.


11
Et à l'extrémité opposée, vous ne pouvez pas utiliser l'opérateur [] sur une carte const
David Rodríguez - dribeas

2
+1 pour Nick, les gens peuvent devenir fous s'ils ne le savent pas .find().
LiraNuna

ou " const map::operator[]génère des messages d'erreur"
juste quelqu'un

2
Ce n'est pas une fonctionnalité du langage, c'est une fonctionnalité de la bibliothèque de modèles Standard. C'est aussi assez évident, puisque l'opérateur [] renvoie une référence valide.
Ramon Zarazua B.26

2
J'ai dû utiliser des cartes en C # pendant un certain temps, où les cartes ne se comportent pas de cette façon, afin de réaliser que c'est une fonctionnalité. Je pensais que cela m'ennuyait plus que je ne l'utilisais, mais il semble que j'avais tort. Il me manque dans C #.
sbi

20

Le fait de placer des fonctions ou des variables dans un espace de noms sans nom rend obsolète l'utilisation de staticpour les limiter à l'étendue du fichier.


«obsolète» est un terme fort…
Potatoswatter

@Potato: Ancien commentaire, je sais, mais la norme dit que l'utilisation de statique dans la portée de l'espace de noms est obsolète, avec une préférence pour les espaces de noms sans nom.
GManNickG

@GMan: pas de problème, je ne pense pas que les pages SO "meurent" vraiment. Juste pour les deux côtés de l'histoire, staticla portée mondiale n'est en aucun cas obsolète. (Pour référence: C ++ 03 §D.2)
Potatoswatter

Ah, à lire de plus près, "Un nom déclaré dans l'espace de noms global a une portée d'espace de noms global (également appelée portée globale)." Cela veut-il vraiment dire cela?
Potatoswatter

@Potato: Ouais. :) staticuse ne doit être utilisé que dans un type de classe ou une fonction.
GManNickG

19

La définition des fonctions d'amis ordinaires dans les modèles de classe nécessite une attention particulière:

template <typename T> 
class Creator { 
    friend void appear() {  // a new function ::appear(), but it doesn't 
                           // exist until Creator is instantiated 
    } 
};
Creator<void> miracle;  // ::appear() is created at this point 
Creator<double> oops;   // ERROR: ::appear() is created a second time! 

Dans cet exemple, deux instanciations différentes créent deux définitions identiques - une violation directe de l' ODR

Nous devons donc nous assurer que les paramètres du modèle du modèle de classe apparaissent dans le type de n'importe quelle fonction amie définie dans ce modèle (à moins que nous ne voulions empêcher plus d'une instanciation d'un modèle de classe dans un fichier particulier, mais c'est plutôt improbable). Appliquons ceci à une variante de notre exemple précédent:

template <typename T> 
class Creator { 
    friend void feed(Creator<T>*){  // every T generates a different 
                                   // function ::feed() 
    } 
}; 

Creator<void> one;     // generates ::feed(Creator<void>*) 
Creator<double> two;   // generates ::feed(Creator<double>*) 

Clause de non-responsabilité: J'ai collé cette section à partir de modèles C ++: Le guide complet / Section 8.4


18

les fonctions void peuvent renvoyer des valeurs void

Peu connu, mais le code suivant convient

void f() { }
void g() { return f(); }

Aussi étrange que le suivant

void f() { return (void)"i'm discarded"; }

Sachant cela, vous pouvez en profiter dans certains domaines. Un exemple: les voidfonctions ne peuvent pas renvoyer une valeur, mais vous pouvez également ne pas simplement renvoyer rien, car elles peuvent être instanciées avec non-void. Au lieu de stocker la valeur dans une variable locale, ce qui provoquera une erreur pour void, renvoyez simplement une valeur directement

template<typename T>
struct sample {
  // assume f<T> may return void
  T dosomething() { return f<T>(); }

  // better than T t = f<T>(); /* ... */ return t; !
};

17

Lire un fichier dans un vecteur de chaînes:

 vector<string> V;
 copy(istream_iterator<string>(cin), istream_iterator<string>(),
     back_inserter(V));

istream_iterator


8
Ou: vector <string> V ((istream_iterator <string> (cin)), istream_iterator <string>);
UncleBens

5
vous voulez dire vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());- parenthèses manquantes après le deuxième
paramètre

1
Ce n'est pas vraiment une fonctionnalité C ++ cachée. Plus d'une fonctionnalité STL. STL! = Une langue
Nick Bedford

14

Vous pouvez modéliser des champs de bits.

template <size_t X, size_t Y>
struct bitfield
{
    char left  : X;
    char right : Y;
};

Je n'ai pas encore trouvé d'objectif pour cela, mais cela m'a vraiment surpris.


1
Voir ici, où je l'ai récemment suggéré pour l' arithmétique n bits: stackoverflow.com/questions/8309538/...
sehe

14

L'une des grammaires les plus intéressantes de tous les langages de programmation.

Trois de ces choses vont ensemble, et deux sont quelque chose de complètement différent ...

SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));

Tous sauf le troisième et le cinquième définissent un SomeTypeobjet sur la pile et l'initialisent (avec udans les deux premiers cas, et le constructeur par défaut dans le quatrième. Le troisième déclare une fonction qui ne prend aucun paramètre et renvoie a SomeType. Le cinquième déclare de la même manière une fonction qui prend un paramètre par valeur de type SomeTypenommé u.


y a-t-il une différence entre le 1er et le 2ème? cependant, je sais que ce sont tous les deux des initialisations.
Özgür

Comptrol: Je ne pense pas. Les deux finiront par appeler le constructeur de copie, même si le premier LOOKS comme l'opérateur d'affectation, c'est vraiment le constructeur de copie.
abelenky

1
Si u est un type différent de SomeType, alors le premier appellera d'abord le constructeur de conversion, puis le constructeur de copie, tandis que le second n'appellera que le constructeur de conversion.
Eclipse

3
Le premier est un appel implicite du constructeur, le second est un appel explicite. Regardez le code suivant pour voir la différence: #include <iostream> class sss {public: explicit sss (int) {std :: cout << "int" << std :: endl; }; sss (double) {std :: cout << "double" << std :: endl; }; }; int main () {sss ddd (7); // appelle le constructeur int sss xxx = 7; // appelle le double constructeur return 0; }
Kirill V. Lyadvinsky

True - la première ligne ne fonctionnera pas si le constructeur est déclaré explicite.
Eclipse

12

Se débarrasser des déclarations anticipées:

struct global
{
     void main()
     {
           a = 1;
           b();
     }
     int a;
     void b(){}
}
singleton;

Écrire des instructions de commutation avec les opérateurs?::

string result = 
    a==0 ? "zero" :
    a==1 ? "one" :
    a==2 ? "two" :
    0;

Tout faire sur une seule ligne:

void a();
int b();
float c = (a(),b(),1.0f);

Remise à zéro des structures sans memset:

FStruct s = {0};

Normalisation / enroulement des valeurs d'angle et de temps:

int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150

Attribution de références:

struct ref
{
   int& r;
   ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;

2
FStruct s = {};est encore plus court.
Constantin

Dans le dernier exemple, ce serait plus simple avec: a (); b (); float c = 1.0f;
Zifre

2
Cette syntaxe "float c = (a (), b (), 1.0f);" est utile pour accentuer l'opération d'affectation (affectation de "c"). Les opérations d'affectation sont importantes dans la programmation car elles sont moins susceptibles de devenir obsolètes à l'OMI. Je ne sais pas pourquoi, cela pourrait être lié à la programmation fonctionnelle où l'état du programme est réaffecté à chaque image. PS. Et non, "int d = (11,22,1.0f)" sera égal à "1". Testé il y a une minute avec VS2008.
AareP

2
+1 Ne devriez-vous pas appeler main ? Je suggère global().main();et simplement oublier le singleton ( vous pouvez simplement travailler avec le temporaire, qui obtient la vie de ce étendu )
sehe

1
Je doute que l'attribution de références soit portable. J'adore la structure pour renoncer aux déclarations anticipées.
Thomas Eding

12

L'opérateur conditionnel ternaire ?:exige que ses deuxième et troisième opérandes aient des types "agréables" (parlant de manière informelle). Mais cette exigence a une exception (jeu de mots): le deuxième ou le troisième opérande peut être une expression throw (qui a un type void), quel que soit le type de l'autre opérande.

En d'autres termes, on peut écrire les expressions C ++ parfaitement valides suivantes en utilisant l' ?:opérateur

i = a > b ? a : throw something();

BTW, le fait que l'expression throw soit en fait une expression (de type void) et non une instruction est une autre caractéristique peu connue du langage C ++. Cela signifie, entre autres, que le code suivant est parfaitement valide

void foo()
{
  return throw something();
}

bien qu'il n'y ait pas grand chose à faire de cette façon (peut-être que dans un code de modèle générique, cela pourrait être utile).


Pour ce que ça vaut, Neil a une question à ce sujet: stackoverflow.com/questions/1212978/… , juste pour plus d'informations.
GManNickG

12

La règle de dominance est utile, mais peu connue. Il dit que même si dans un chemin non unique à travers un treillis de classe de base, la recherche de nom pour un membre partiellement caché est unique si le membre appartient à une classe de base virtuelle:

struct A { void f() { } };

struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };

// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };

J'ai utilisé cela pour implémenter un support d'alignement qui détermine automatiquement l'alignement le plus strict au moyen de la règle de dominance.

Cela ne s'applique pas seulement aux fonctions virtuelles, mais également aux noms de typedef, aux membres statiques / non virtuels et à toute autre chose. Je l'ai vu utilisé pour implémenter des traits écrasables dans les méta-programmes.


1
Soigné. Une raison particulière que vous avez incluse struct Cdans votre exemple ...? À votre santé.
Tony Delroy
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.