web-dev-qa-db-fra.com

Fonctionnalités cachées de C ++?

Pas d’amour C++ en ce qui concerne les questions cachées de la série? Je pensais que je le lancerais là-bas. Quelles sont certaines des fonctionnalités cachées de C++?

114
Craig H

La plupart des programmeurs C++ connaissent l'opérateur ternaire:

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

Cependant, ils ne réalisent pas que cela 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 :-)

308
Ferruccio

Vous pouvez insérer des URI dans une source C++ sans erreur. Par exemple:

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

    ...
}
238
Ben

Pointeur arithmétique.

Les programmeurs C++ préfèrent éviter les pointeurs en raison des bogues pouvant être introduits.

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

140
Anonymouse

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

La plupart de ces installations ne sont pas des fonctionnalités intégrées à la langue, mais des fonctionnalités basées sur des bibliothèques.

Le plus important est le [~ # ~] raii [~ # ~] , souvent ignoré pendant des années par les développeurs C++ du monde C. La surcharge d'opérateur est souvent une fonctionnalité mal comprise qui permet à la fois un comportement semblable à un tableau (opérateur d'indice), un pointeur ressemblant à des opérations (pointeurs intelligents) et un élément intégré. opérations (multiplier les matrices.

L'utilisation de exception est souvent difficile, mais avec quelques travaux, peut produire un code très robuste grâce à exception safety spécifications (y compris un code qui n'échouera pas ou qui aura une fonctionnalité de validation qui réussira ou reviendra à son état d'origine).

La plus célèbre des fonctionnalités "cachées" de C++ est la métaprogrammation des modèles , car elle vous permet de faire exécuter votre programme partiellement (ou totalement) au moment de la compilation. au lieu de l'exécution. Ceci est difficile, cependant, et vous devez bien maîtriser les modèles avant de l’essayer.

D'autres utilisent le paradigme multiple pour produire des "méthodes de programmation" 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 en étant stateful. 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 figurer dans la liste des "paradigmes officiels C++".

En utilisant des modèles , vous pouvez générer un code qui fonctionnera pour 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 type automatisé malloc/realloc/free). Les objets C++ sont vraiment puissants (et donc dangereux s'ils sont utilisés sans précaution), mais même le polymorphisme dynamique a sa version statique en C++: le [~ # ~] crtp [~ # ~] .

J'ai trouvé que la plupart des livres de type " Effective C++ " de Scott Meyers ou " exceptionnels C++ " de Herb Sutter être à la fois facile à lire et riche en informations sur les fonctionnalités connues et moins connues du C++.

Parmi mes préférés est celui qui devrait faire lever les cheveux de Java) de l’horreur: en C++, est le moyen le plus orienté objet d’ajouter une fonctionnalité à un objet passe par une fonction non membre non ami, au lieu d'une fonction membre (méthode de classe), car:

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

  • les fonctions non membres non amis n'ont pas d'accès privilégié à la classe internal. En tant que tel, utiliser une fonction membre sur une fonction non-membre non amie 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 de la semaine en ligne de Herb Sutter, n ° 84: http://www.gotw.ca/gotw/084.htm )

119
paercebal

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

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );
118
Jason Mock

Les variables peuvent non seulement être déclarées dans la partie init d'une boucle for, mais également des classes et des fonctions.

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

Cela permet de multiples variables de types différents.

102

L'opérateur de tableau est associatif.

A [8] est un synonyme de * (A + 8). Comme l'addition est associative, elle peut être réécrite sous la forme * (8 + A), synonyme de ..... 8 [A]

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

77
Colin Jensen

Une chose peu connue est que les syndicats 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 de ce qui a trait à l'héritage (y compris les fonctions virtuelles).

73

C++ est un standard, il ne devrait y avoir aucune fonctionnalité cachée ...

C++ est un langage multi-paradigme, vous pouvez parier votre dernier argent sur des fonctionnalités cachées. Un exemple parmi tant d'autres: métaprogrammation du modèle . Personne dans le comité de normalisation n'a voulu créer un sous-langage complet de Turing exécuté au moment de la compilation.

72
Konrad Rudolph

Une autre fonctionnalité cachée qui ne fonctionne pas en C est la fonctionnalité du unaire + opérateur. 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érateur qui avait auparavant son type d’énumération a maintenant le type entier parfait qui peut correspondre à sa valeur. Manuellement, vous sauriez à peine ce type! Ceci 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 intégré à la classe sans définition sortante de la classe, mais parfois, la liaison échoue? L'opérateur peut aider à créer un temporaire sans créer 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écomposer un tableau en un pointeur

Voulez-vous transmettre deux pointeurs à une fonction, mais cela ne fonctionnera 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
}
66

La durée de vie des temporaries liés à des références const est celle que peu de gens connaissent. Ou du moins c'est mon élément de connaissance C++ préféré que la plupart des gens ne connaissent pas.

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

Une fonctionnalité intéressante qui n'est pas souvent utilisée est le bloc try-catch pour toute la fonction:

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

L’utilisation principale serait de traduire une exception en une autre classe d’exception et de la renvoyer, ou de faire la traduction entre les exceptions et la gestion du code d’erreur basé sur le retour.

52
vividos

Beaucoup connaissent la métafonction identity/id, mais il existe une casse utilisateur Nice pour les observations sans modèle: Déclarations simplifiées:

// 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; };
44

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

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

Certaines macros utilisent cela, par exemple pour fournir une étendue "verrouillée" comme ceci:

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, c'est non seulement possible dans un if, mais aussi dans un commutateur:

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

et dans une boucle while:

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

(et aussi dans un état). Mais je ne suis pas sûr de savoir si tout cela est utile :)

43

Empêcher les opérateurs de virgule d'appeler des surcharges d'opérateurs

Parfois, vous utilisez correctement l’opérateur virgule, mais vous voulez vous assurer qu’aucun opérateur de virgule défini par l’utilisateur n’interfère, car vous vous appuyez par exemple sur des points de séquence situés à gauche ou à droite, ou vous voulez vous assurer que rien n’interfère avec les paramètres souhaités. action. C'est ici que void() entre en jeu:

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

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

29

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

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 en tant que:

clName::clName() : a()
{
}
28
Poorna

Oooh, je peux créer une liste de détestes d'animaux:

  • Les destructeurs doivent être virtuels si vous avez l'intention d'utiliser polymorphiquement
  • 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 (ce qui les rend moins utiles)
  • spécificateurs d'exception: ont l'air utiles, mais ne le sont pas
  • les surcharges de fonctions masquent les fonctions de classe de base avec différentes signatures.
  • pas de normalisation utile sur l'internationalisation (jeu de caractères large standard standard portable, ça ne va pas? Il faudra attendre jusqu'à C++ 0x)

Du coté positif

  • fonctionnalité cachée: fonction essayer des blocs. Malheureusement, je n'en ai trouvé aucune utilité. Oui, je sais pourquoi ils l'ont ajouté, mais vous devez rediffuser dans un constructeur, ce qui le rend inutile.
  • Il convient de regarder attentivement les garanties de la STL relatives à la validité de l’itérateur après la modification du conteneur, ce qui peut vous permettre de réaliser des boucles légèrement plus agréables.
  • Boost - ce n'est pas un secret mais ça vaut la peine d'être utilisé.
  • Optimisation de la valeur de retour (pas évident, mais cela est spécifiquement autorisé par le standard)
  • Foncteurs aka objets de fonction aka opérateur (). Ceci est largement utilisé par la STL. Ce n'est pas vraiment un secret, mais c'est un effet secondaire astucieux de la surcharge de l'opérateur et des modèles.
27
Robert

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

Normalement, C++ vous interdit d'accéder aux membres protégés non statiques d'un objet de 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 à quoi se réfère réellement la référence. Il peut s'agir d'un objet C, auquel cas la classe B n'a aucune activité ni indice sur ses données. Cet accès n’est accordé que si x est 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 constituant simplement une classe "jetable" qui lit les membres, par exemple de 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 beaucoup 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 ce membre - et non à la classe que vous avez spécifiée lors de la prise de 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 aussi avec le std::stack Exemple.

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, ce 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);
}
27

Caractéristiques cachées:

  1. Les fonctions virtuelles pures peuvent avoir une implémentation. Exemple commun, destructeur virtuel pur.
  2. Si une fonction lève une exception non répertoriée dans ses spécifications, mais que la fonction a std::bad_exception dans sa spécification d’exception, l’exception est convertie en std::bad_exception et jeté automatiquement. De cette façon, vous saurez au moins qu'un bad_exception a été jeté. Lire la suite ici .

  3. fonction essayer des blocs

  4. Le mot clé template dans des typedefs non ambiguës dans un modèle de classe. Si le nom d'une spécialisation de modèle de membre apparaît après un ., ->, ou :: opérateur, et ce nom ayant explicitement qualifié les paramètres de modèle, préfixe le nom de modèle de membre avec le mot-clé modèle. Lire la suite ici .

  5. les paramètres par défaut des paramètres de fonction peuvent être modifiés au moment de l'exécution. Lire la suite 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 invoquée sur un objet temporaire. Par exemple:

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

    Lire la suite ici .

  8. Si deux types différents sont présents avant et après le : dans le ternaire (?:), 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)
    }
    
26
Sumant

Une autre caractéristique cachée est que vous pouvez appeler des objets de classe pouvant être convertis en pointeurs de fonction ou en références. La résolution de surcharge est faite sur le 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".

26

map::operator[] crée une entrée si la clé est manquante et renvoie la référence à la valeur d'entrée construite par défaut. Pour que vous puissiez é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 surpris de voir combien de programmeurs C++ ne le savent pas.

24
Constantin

Placer des fonctions ou des variables dans un espace de noms sans nom décourage l'utilisation de static pour les restreindre à la portée du fichier.

20
Jim Hunziker

La définition de 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 ODR

Nous devons donc nous assurer que les paramètres de modèle du modèle de classe apparaissent dans le type de toute fonction ami définie dans ce modèle (sauf si nous souhaitons empêcher plus d'une instanciation d'un modèle de classe dans un fichier particulier, mais cela 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>*) 

Disclaimer: J'ai collé cette section de Modèles C++: Le Guide complet /Section 8.4

19
Özgür

les fonctions void peuvent retourner des valeurs void

Peu connu, mais le code suivant est correct

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

Aussi étrange que le regard suivant

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

Sachant cela, vous pouvez en tirer parti dans certains domaines. Un exemple: les fonctions void ne peuvent pas renvoyer de valeur, mais vous pouvez également ne rien renvoyer, 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, il suffit de renvoyer 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; !
};
18

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

17
Jason Baker

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 objet SomeType sur la pile et l'initialisent (avec u dans les deux premiers cas, et le constructeur par défaut dans le quatrième. Le troisième est la déclaration d'une fonction qui ne prend aucun paramètre et retourne un SomeType. Le cinquième déclare de la même manière une fonction qui prend un paramètre avec une valeur de type SomeType nommée u.

14
Eclipse

Vous pouvez gabarit 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'objet à cela, mais cela m'a vraiment surpris.

14
Kaz Dragon

La règle de dominance est utile, mais peu connue. Il indique que même si, dans un chemin non unique passant par un réseau de classe de base, la recherche de nom d'un membre partiellement masqué 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 l'alignement-support qui détermine automatiquement l'alignement le plus strict au moyen de la règle de dominance.

Ceci ne s'applique pas seulement aux fonctions virtuelles, mais également aux noms typedef, aux membres statiques/non virtuels et à tout le reste. implémenter des traits écrasables dans les méta-programmes.

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 comporte une exception (jeu de mots): le deuxième ou le troisième opérande peut être une expression throw (de type void), quel que soit le type de l’autre opérande.

En d’autres termes, on peut écrire les expressions C++ proprement valables suivantes à l’aide du ?: opérateur

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

En passant, le fait que l’expression d’une expression soit réellement ne expression (de type void) et non une instruction est une autre caractéristique peu connue du langage C++. Cela signifie, entre autres choses, que le code suivant est parfaitement valide

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

bien que cela ne soit pas très utile de le faire de cette façon (peut-être que dans un code de modèle générique, cela pourrait être utile).

12
AnT

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

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

Écrire des instructions de changement avec?: 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);

Mise à zéro des structures sans memset:

FStruct s = {0};

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

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

Assigner des références:

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

J'ai trouvé que ce blog était une ressource incroyable sur les arcanes du C++: C++ Truths .

9
Drealmer

Un secret dangereux est

Fred* f = new(ram) Fred(); http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10
f->~Fred();

Mon secret préféré que je vois rarement utilisé:

class A
{
};

struct B
{
  A a;
  operator A&() { return a; }
};

void func(A a) { }

int main()
{
  A a, c;
  B b;
  a=c;
  func(b); //yeah baby
  a=b; //gotta love this
}
8
user34537

Les cours locaux sont géniaux:

struct MyAwesomeAbstractClass
{ ... };


template <typename T>
MyAwesomeAbstractClass*
create_awesome(T param)
{
    struct ans : MyAwesomeAbstractClass
    {
        // Make the implementation depend on T
    };

    return new ans(...);
}

plutôt chouette, puisqu'il ne pollue pas l'espace de noms avec des définitions de classe inutiles ...

8
Alexandre C.

Une fonctionnalité cachée, même cachée à développeurs GCC , consiste à initialiser un membre du tableau à l'aide d'un littéral de chaîne. Supposons que votre structure doit fonctionner avec un tableau C et que vous souhaitez initialiser le membre du tableau avec un contenu par défaut.

struct Person {
  char name[255];
  Person():name("???") { }
};

Cela fonctionne et ne fonctionne qu'avec les tableaux de caractères et les initialiseurs littéraux de chaîne. Pas besoin de strcpy!

7

Un exemple parmi d'autres: la métaprogrammation des modèles. Personne dans le comité de normalisation n'a voulu créer un sous-langage complet de Turing exécuté au moment de la compilation.

La métaprogrammation des modèles n’est guère une fonctionnalité cachée. C'est même dans la bibliothèque de boost. Voir MPL . Mais si "presque caché" suffit, jetez un coup d'oeil aux librairies boost . Il contient de nombreux objets qui ne sont pas facilement accessibles sans le support d’une bibliothèque puissante.

Un exemple est la librairie boost.lambda , ce qui est intéressant car C++ n'a pas de fonctions lambda dans le standard actuel.

Un autre exemple est Loki , qui "utilise beaucoup la métaprogrammation de modèles C++ et implémente plusieurs outils couramment utilisés: liste de types, foncteur, singleton, pointeur intelligent, fabrique d'objets, visiteur et méthodes multiples". [ Wikipedia ]

6
Markowitch

Il n'y a pas de fonctionnalités cachées, mais le langage C++ est très puissant et même les développeurs de standards ne pouvaient souvent pas imaginer ce que le C++ pouvait utiliser.

En fait, à partir d'une construction de langage assez simple, vous pouvez écrire quelque chose de très puissant. De nombreux exemples sont disponibles sur le site www.boost.org à titre d'exemples (et http://www.boost.org/doc/libs/1_36_0/doc/html/lambda.html parmi eux ).

Il est bon de comprendre comment il est possible de combiner une construction de langage simple à quelque chose de puissant "Modèles C++: le guide complet" de David Vandevoorde, Nicolai M. Josuttis et livre vraiment magique "Design moderne en C++ ..." de Andrei Alexandresc .

Et enfin, il est difficile d’apprendre le C++, vous devriez essayer de le remplir;)

5
sergtk

Il me semble que peu de gens connaissent l'existence d'espaces de noms non nommés:

namespace {
  // Classes, functions, and objects here.
}

Les espaces de noms non nommés se comportent comme s'ils étaient remplacés par:

namespace __unique_{ /* empty body */ }
using namespace __unique_name__;
namespace __unique_{
  // original namespace body
}

".. où toutes les occurrences de [ce nom unique] dans une unité de traduction sont remplacées par le même identifiant et que cet identifiant diffère de tous les autres identifiants du programme entier." [C++ 03, 7.3.1.1/1]

4
vobject
4
Özgür

Je ne suis pas sûr de ce qui est caché, mais il y a quelques intéressant'astuces' qui ne sont probablement pas évidents à la lecture des spécifications.

3
dbrien

Il y a beaucoup de "comportement indéfini". Vous pouvez apprendre à les empêcher de lire de bons livres et de lire les normes.

3
ugasoft

La plupart des développeurs C++ ignorent la puissance de la métaprogrammation des modèles. Départ Loki Libary . Il implémente plusieurs outils avancés comme la liste de caractères, le foncteur, le singleton, le pointeur intelligent, la fabrique d’objets, le visiteur et des méthodes multiples en utilisant une métaprogrammation étendue des modèles (from wikipedia ). Pour la plupart, vous pouvez les considérer comme des fonctionnalités "cachées" de c ++.

3
Sridhar Iyer

De vérités C++ .

Définir des fonctions ayant des signatures identiques dans le même périmètre est donc légal:

template<class T> // (a) a base template
void f(T) {
  std::cout << "f(T)\n";
}

template<>
void f<>(int*) { // (b) an explicit specialization
  std::cout << "f(int *) specilization\n";
}

template<class T> // (c) another, overloads (a)
void f(T*) {
  std::cout << "f(T *)\n";
}

template<>
void f<>(int*) { // (d) another identical explicit specialization
  std::cout << "f(int *) another specilization\n";
}
3
Özgür
  • pointeurs vers les méthodes de classe
  • Le mot-clé "typename"
3
shoosh
3
sdcvvc

main () n'a pas besoin d'une valeur de retour:

int main(){}

est le programme C++ valide le plus court.

2
Jeffrey Faust

Faites attention à la différence entre le pointeur de fonction libre et les initialisations de pointeur de fonction membre:

fonction membre:

struct S
{
 void func(){};
};
int main(){
void (S::*pmf)()=&S::func;//  & is mandatory
}

et fonction libre:

void func(int){}
int main(){
void (*pf)(int)=func; // & is unnecessary it can be &func as well; 
}

Grâce à ce & redondant, vous pouvez ajouter des manipulateurs de flux - qui sont des fonctions libres - en chaîne sans lui:

cout<<hex<<56; //otherwise you would have to write cout<<&hex<<56, not neat.
2
Özgür
  1. map::insert(std::pair(key, value)); n'écrase pas si la valeur de clé existe déjà.

  2. Vous pouvez instancier une classe immédiatement après sa définition:

    class MyClass {public: /* code */} myClass;
    
2
Viktor Sehr

Il y a des tonnes de constructions "difficiles" en C++. Ils vont de "simples" implémentations de classes scellées/finales en utilisant l'héritage virtuel. Et obtenez des constructions de méta-programmation assez "complexes" telles que celles de Boost MPL ( tutoriel ). Les possibilités de se tirer dans le pied sont infinies, mais si elles sont maîtrisées (par exemple, des programmeurs expérimentés), procurez-vous l'une des meilleures flexibilités en termes de maintenabilité et de performances.

1
Amir

La classe et la clé de classe sont presque identiques. La principale différence est que les classes ont par défaut un accès privé pour les membres et les bases, tandis que les structures utilisent par défaut public:

// this is completely valid C++:
class A;
struct A { virtual ~A() = 0; };
class B : public A { public: virtual ~B(); };

// means the exact same as:
struct A;
class A { public: virtual ~A() = 0; };
struct B : A { virtual ~B(); };

// you can't even tell the difference from other code whether 'struct'
// or 'class' was used for A and B

Les syndicats peuvent également avoir des membres et des méthodes, ainsi qu'un accès public par défaut, de la même manière que les structures.

1
a_m0d

idiome de conversion indirecte :

Supposons que vous conceviez une classe de pointeur intelligent. En plus de surcharger les opérateurs * et ->, une classe de pointeur intelligent définit généralement un opérateur de conversion en bool:

template <class T>
class Ptr
{
public:
 operator bool() const
 {
  return (rawptr ? true: false);
 }
//..more stuff
private:
 T * rawptr;
};

La conversion en bool permet aux clients d'utiliser des pointeurs intelligents dans des expressions nécessitant des opérandes bool:

Ptr<int> ptr(new int);
if(ptr ) //calls operator bool()
 cout<<"int value is: "<<*ptr <<endl;
else
 cout<<"empty"<<endl;

De plus, la conversion implicite en bool est requise dans les déclarations conditionnelles telles que:

if (shared_ptr<X> px = dynamic_pointer_cast<X>(py))
{
 //we get here only of px isn't empty
} 

Hélas, cette conversion automatique ouvre la porte à de mauvaises surprises:

Ptr <int> p1;
Ptr <double> p2;

//surprise #1
cout<<"p1 + p2 = "<< p1+p2 <<endl; 
//prints 0, 1, or 2, although there isn't an overloaded operator+()

Ptr <File> pf;
Ptr <Query> pq; // Query and File are unrelated 

//surprise #2
if(pf==pq) //compares bool values, not pointers! 

Solution: utilisez l'idiome "conversion indirecte", en convertissant le pointeur en membre de données [pMember] en bool afin qu'il n'y ait qu'une conversion implicite, ce qui empêchera le comportement inattendu susmentionné: pMember-> bool plutôt que bool-> quelque chose autre.

1
Özgür

Si l'opérateur delete () prend l'argument de taille en plus de * void, cela signifie qu'il s'agira d'une classe de base. Cet argument de taille rend possible la vérification de la taille des types afin de détruire le bon. Voici ce que Stephen Dewhurst en dit:

Notez également que nous avons utilisé une version à deux arguments d'opérateur delete plutôt que la version à un argument habituelle. Cette version à deux arguments est une autre version "habituelle" de membre opérateur delete souvent utilisée par les classes de base qui s'attendent à ce que les classes dérivées héritent de l'implémentation de l'opérateur delete. Le second argument contiendra la taille de l'objet à supprimer, informations souvent utiles pour la mise en œuvre de la gestion de la mémoire personnalisée.

1
Özgür

Je trouve les instatiations de modèles récursives plutôt cool:

template<class int>
class foo;

template
class foo<0> {
    int* get<0>() { return array; }
    int* array;  
};

template<class int>
class foo<i> : public foo<i-1> {
    int* get<i>() { return array + 1; }  
};

Je l'ai utilisé pour générer une classe avec 10-15 fonctions qui renvoient des pointeurs dans différentes parties d'un tableau, puisqu'un API que j'ai utilisé nécessitait un pointeur de fonction pour chaque valeur.

C'est à dire. programmer le compilateur pour générer un tas de fonctions, via la récursivité. C'est de la tarte. :)

1
Macke

Mon préféré (pour le moment) est le manque de sématique dans un énoncé comme A = B = C. Qu'est-ce que la valeur de A est fondamentalement indéterminée.

Pensez à ceci:

class clC
{
public:
   clC& operator=(const clC& other)
   {
      //do some assignment stuff
      return copy(other);
   }
   virtual clC& copy(const clC& other);
}

class clB : public clC
{
public:
  clB() : m_copy()
  {
  }

  clC& copy(const clC& other)
  {
    return m_copy;
  }

private:
  class clInnerB : public clC
  {
  }
  clInnerB m_copy;
}

maintenant, A pourrait être d'un type inaccessible à autre chose que des objets de type clB et avoir une valeur sans rapport avec C.

0
Rune FS

Ajout de contraintes aux modèles.

0
Özgür

Pointeurs membres et opérateur de pointeur membre -> *

#include <stdio.h>
struct A { int d; int e() { return d; } };
int main() {
    A* a = new A();
    a->d = 8;
    printf("%d %d\n", a ->* &A::d, (a ->* &A::e)() );
    return 0;
}

Pour les méthodes (a -> * & A :: e) () est un peu similaire à Function.call () de javascript.

var f = A.e
f.call(a) 

Pour les membres, c'est un peu comme accéder à l'opérateur []

a['d']
0
Kamil Szot

Vous pouvez afficher toutes les macros prédéfinies à l'aide de commutateurs de ligne de commande avec certains compilateurs. Cela fonctionne avec gcc et icc (compilateur C++ d'Intel):

$ touch empty.cpp
$ g++ -E -dM empty.cpp | sort >gxx-macros.txt
$ icc -E -dM empty.cpp | sort >icx-macros.txt
$ touch empty.c
$ gcc -E -dM empty.c | sort >gcc-macros.txt
$ icc -E -dM empty.c | sort >icc-macros.txt

Pour MSVC ils sont listés dans un single place . Elles pourraient également être documentées dans un endroit unique pour les autres, mais avec les commandes ci-dessus, vous pouvez clairement voir ce qui est défini et non défini et ce les valeurs sont utilisées après l'application de tous les autres commutateurs de ligne de commande.

Comparer (après le tri):

 $ diff gxx-macros.txt icx-macros.txt
 $ diff gxx-macros.txt gcc-macros.txt
 $ diff icx-macros.txt icc-macros.txt
0
Roger Pate
class Empty {};

namespace std {
  // #1 specializing from std namespace is okay under certain circumstances
  template<>
  void swap<Empty>(Empty&, Empty&) {} 
}

/* #2 The following function has no arguments. 
   There is no 'unknown argument list' as we do
   in C.
*/
void my_function() { 
  cout << "whoa! an error\n"; // #3 using can be scoped, as it is in main below
  // and this doesn't affect things outside of that scope
}

int main() {
  using namespace std; /* #4 you can use using in function scopes */
  cout << sizeof(Empty) << "\n"; /* #5 sizeof(Empty) is never 0 */
  /* #6 falling off of main without an explicit return means "return 0;" */
}
0
dirkgently