Recherche…


Syntaxe

  • variable.member_var = constante;
  • variable.member_function ();
  • variable_pointer-> member_var = constant;
  • variable_pointer-> member_function ();

Remarques

Notez que la seule différence entre les mots-clés struct et class est que, par défaut, les variables membres, les fonctions membres et les classes de base d'une struct sont public , alors que dans une class elles sont private . Les programmeurs C ++ ont tendance à l'appeler une classe si elle a des constructeurs et des destructeurs, et la possibilité d'imposer ses propres invariants; ou une structure si ce n'est qu'une simple collection de valeurs, mais le langage C ++ lui-même ne fait aucune distinction.

Les bases de la classe

Une classe est un type défini par l'utilisateur. Une classe est introduite avec le mot-clé class , struct ou union . En usage familier, le terme "classe" désigne généralement uniquement les classes non syndiquées.

Une classe est une collection de membres de classe , qui peuvent être:

  • les variables membres (également appelées "champs"),
  • fonctions membres (également appelées "méthodes"),
  • types de membres ou typedefs (par exemple "classes imbriquées"),
  • gabarits de membres (de tout type: variable, fonction, classe ou modèle d'alias)

Les mots struct clés class et struct , appelés les clés de classe , sont largement interchangeables, sauf que le spécificateur d'accès par défaut pour les membres et les bases est "private" pour une classe déclarée avec la clé class et "public" pour la classe déclarée avec struct ou union (cf. modificateurs d’accès ).

Par exemple, les extraits de code suivants sont identiques:

struct Vector
{
    int x;
    int y;
    int z;
};
// are equivalent to
class Vector
{
public:
    int x;
    int y;
    int z;
};

En déclarant une classe`, un nouveau type est ajouté à votre programme, et il est possible d'instancier des objets de cette classe par

Vector my_vector;

Les membres d'une classe sont accessibles à l'aide de la syntaxe à points.

my_vector.x = 10;
my_vector.y = my_vector.x + 1; // my_vector.y = 11;
my_vector.z = my_vector.y - 4; // my:vector.z = 7;

Spécificateurs d'accès

Trois mots - clés agissent comme spécificateurs d'accès . Celles-ci limitent l'accès aux membres de la classe après le spécificateur, jusqu'à ce qu'un autre spécificateur modifie à nouveau le niveau d'accès:

Mot-clé La description
public Tout le monde a accès
protected Seule la classe elle-même, les classes dérivées et les amis ont accès
private Seule la classe elle-même et ses amis ont accès

Lorsque le type est défini à l'aide du mot class clé class , le spécificateur d'accès par défaut est private , mais si le type est défini à l'aide du mot struct clé struct , le spécificateur d'accès par défaut est public :

struct MyStruct { int x; };
class MyClass { int x; };

MyStruct s;
s.x = 9; // well formed, because x is public

MyClass c;
c.x = 9; // ill-formed, because x is private

Les spécificateurs d'accès sont principalement utilisés pour limiter l'accès aux champs et méthodes internes, et obligent le programmeur à utiliser une interface spécifique, par exemple pour forcer l'utilisation de getters et de setters au lieu de référencer directement une variable:

class MyClass {

public: /* Methods: */

    int x() const noexcept { return m_x; }
    void setX(int const x) noexcept { m_x = x; }

private: /* Fields: */

    int m_x;

};

L'utilisation de protected est utile pour permettre à certaines fonctionnalités du type d'être uniquement accessibles aux classes dérivées. Par exemple, dans le code suivant, la méthode calculateValue() est uniquement accessible aux classes dérivées de la classe de base Plus2Base , telle que FortyTwo :

struct Plus2Base {
    int value() noexcept { return calculateValue() + 2; }
protected: /* Methods: */
    virtual int calculateValue() noexcept = 0;
};
struct FortyTwo: Plus2Base {
protected: /* Methods: */
    int calculateValue() noexcept final override { return 40; }
};

Notez que le mot-clé friend peut être utilisé pour ajouter des exceptions d'accès aux fonctions ou aux types d'accès aux membres protégés et privés.

Les mots clés public , protected et private peuvent également être utilisés pour accorder ou limiter l'accès aux sous-objets de classe de base. Voir l'exemple d' héritage .

Héritage

Les classes / structures peuvent avoir des relations d'héritage.

Si une classe / struct B hérite d'une classe / struct A , cela signifie que B a comme parent A On dit que B est une classe / struct dérivée de A , et A est la classe / struct de base.

struct A
{
public:
    int p1;
protected:
    int p2;
private:
    int p3;
};

//Make B inherit publicly (default) from A
struct B : A
{
};

Il existe 3 formes d'héritage pour une classe / struct:

  • public
  • private
  • protected

Notez que l'héritage par défaut est identique à la visibilité par défaut des membres: public si vous utilisez le mot struct clé struct et private pour le mot class clé class .

Il est même possible qu'une class dérive d'une struct (ou vice versa). Dans ce cas, l'héritage par défaut est contrôlé par l'enfant. Par conséquent, une struct dérivant d'une class sera héritée par défaut et une class dérivée d'une struct aura un héritage privé par défaut.

héritage public :

struct B : public A // or just `struct B : A`
{
    void foo()
    {
        p1 = 0; //well formed, p1 is public in B
        p2 = 0; //well formed, p2 is protected in B
        p3 = 0; //ill formed, p3 is private in A
    }
};

B b;
b.p1 = 1; //well formed, p1 is public
b.p2 = 1; //ill formed, p2 is protected
b.p3 = 1; //ill formed, p3 is inaccessible

héritage private :

struct B : private A
{
    void foo()
    {
        p1 = 0; //well formed, p1 is private in B
        p2 = 0; //well formed, p2 is private in B
        p3 = 0; //ill formed, p3 is private in A
    }
};

B b;
b.p1 = 1; //ill formed, p1 is private
b.p2 = 1; //ill formed, p2 is private
b.p3 = 1; //ill formed, p3 is inaccessible

héritage protected :

struct B : protected A
{
    void foo()
    {
        p1 = 0; //well formed, p1 is protected in B
        p2 = 0; //well formed, p2 is protected in B
        p3 = 0; //ill formed, p3 is private in A
    }
};

B b;
b.p1 = 1; //ill formed, p1 is protected
b.p2 = 1; //ill formed, p2 is protected
b.p3 = 1; //ill formed, p3 is inaccessible

Notez que bien que l'héritage protected soit autorisé, son utilisation réelle est rare. Un exemple de la façon dont l'héritage protected est utilisé dans les applications est la spécialisation de classe de base partielle (généralement appelée «polymorphisme contrôlé»).

Lorsque la POO était relativement nouvelle, on disait souvent que l'héritage (public) modélisait une relation "IS-A". En d'autres termes, l'héritage public est correct uniquement si une instance de la classe dérivée est également une instance de la classe de base.

Cela a ensuite été affiné dans le principe de substitution Liskov : l'héritage public ne devrait être utilisé que si / si une instance de la classe dérivée peut être substituée à une instance de la classe de base dans des circonstances possibles (et toujours logique).

On dit généralement que l'héritage privé incarne une relation complètement différente: "est implémenté en termes de" (parfois appelé relation "HAS-A"). Par exemple, une classe Stack peut hériter en privé d'une classe Vector . L'héritage privé ressemble beaucoup plus à l'agrégation qu'à l'héritage public.

L'héritage protégé n'est presque jamais utilisé et il n'y a pas d'accord général sur le type de relation qu'il incarne.

Héritage Virtuel

Lorsque vous utilisez l'héritage, vous pouvez spécifier le mot clé virtual :

struct A{};
struct B: public virtual A{};

Lorsque la classe B a la base virtuelle A cela signifie que A résidera dans la classe d'arbre d'héritage la plus dérivée , et que la classe la plus dérivée est également responsable de l'initialisation de cette base virtuelle:

struct A
{
    int member;
    A(int param)
    {
        member = param;
    }
};

struct B: virtual A
{
    B(): A(5){}
};

struct C: B
{
    C(): /*A(88)*/ {}
};

void f()
{
    C object; //error since C is not initializing it's indirect virtual base `A`
}

Si nous dé-commentons /*A(88)*/ nous n'obtiendrons aucune erreur puisque C initialise maintenant sa base virtuelle indirecte A

Notez également que lorsque nous créons un object variable, la classe la plus dérivée est C , donc C est responsable de la création (appel du constructeur de) A et donc la valeur de A::member est de 88 et non de 5 (comme ce serait le cas si nous étions créer un objet de type B ).

C'est utile pour résoudre le problème des diamants .

  A                                        A   A
 / \                                       |   |
B   C                                      B   C
 \ /                                        \ /
  D                                          D
virtual inheritance                   normal inheritance

B et C héritent tous deux de A , et D hérite de B et C , il y a donc 2 instances de A dans D ! Cela entraîne une ambiguïté lorsque vous accédez à un membre de A à D , car le compilateur n'a aucun moyen de savoir de quelle classe voulez-vous accéder à ce membre (celui dont B hérite ou celui hérité par C ?) .

L'héritage virtuel résout ce problème: comme la base virtuelle ne réside que dans la plupart des objets dérivés, il n'y aura qu'une seule instance de A dans D

struct A
{
    void foo() {}
};

struct B : public /*virtual*/ A {};
struct C : public /*virtual*/ A {};

struct D : public B, public C
{
    void bar()
    {
        foo(); //Error, which foo? B::foo() or C::foo()? - Ambiguous
    }
};

La suppression des commentaires résout l'ambiguïté.

Héritage multiple

Mis à part l'héritage unique:

class A {};
class B : public A {};

Vous pouvez également avoir plusieurs héritages:

class A {};
class B {};
class C : public A, public B {};

C aura maintenant hérité de A et B en même temps.

Remarque: cela peut entraîner une ambiguïté si les mêmes noms sont utilisés dans plusieurs class héritées ou struct . Faites attention!

Ambiguïté dans l'héritage multiple

L'héritage multiple peut être utile dans certains cas mais, parfois, une sorte de rencontre étrange de problèmes lors de l'utilisation de l'héritage multiple.

Par exemple: Deux classes de base ont des fonctions avec le même nom qui ne sont pas remplacées dans la classe dérivée et si vous écrivez du code pour accéder à cette fonction en utilisant un objet de classe dérivée, le compilateur affiche une erreur. Voici un code pour ce type d'ambiguïté dans l'héritage multiple.

class base1
{
  public:
     void funtion( )
     { //code for base1 function }  
};
class base2
{
    void function( )
     { // code for base2 function } 
};

class derived : public base1, public base2
{
    
};

int main()
{
    derived obj;
    
  // Error because compiler can't figure out which function to call 
  //either function( ) of base1 or base2 .   
    obj.function( )  
}

Mais, ce problème peut être résolu en utilisant la fonction de résolution de portée pour spécifier quelle fonction classer soit base1, soit base2:

int main()
{
    obj.base1::function( );  // Function of class base1 is called. 
    obj.base2::function( );  // Function of class base2 is called.
}

Accéder aux membres de la classe

Pour accéder aux variables membres et aux fonctions membres d'un objet d'une classe, le . l'opérateur est utilisé:

struct SomeStruct {
  int a;
  int b;
  void foo() {}
};

SomeStruct var;
// Accessing member variable a in var.
std::cout << var.a << std::endl;
// Assigning member variable b in var.
var.b = 1;
// Calling a member function.
var.foo();

Lors de l'accès aux membres d'une classe via un pointeur, l'opérateur -> est couramment utilisé. Alternativement, l'instance peut être déréférencée et le . opérateur utilisé, bien que ce soit moins fréquent:

struct SomeStruct {
  int a;
  int b;
  void foo() {}
};

SomeStruct var;
SomeStruct *p = &var;
// Accessing member variable a in var via pointer.
std::cout << p->a << std::endl;
std::cout << (*p).a << std::endl;
// Assigning member variable b in var via pointer.
p->b = 1;
(*p).b = 1;
// Calling a member function via a pointer.
p->foo();
(*p).foo();

Lors de l'accès aux membres de classe statiques, l'opérateur :: est utilisé, mais sur le nom de la classe au lieu d'une instance de celle-ci. Il est également possible d'accéder au membre statique à partir d'une instance ou d'un pointeur vers une instance à l'aide de . ou -> opérateur, respectivement, avec la même syntaxe que l'accès à des membres non statiques.

struct SomeStruct {
  int a;
  int b;
  void foo() {}

  static int c;
  static void bar() {}
};
int SomeStruct::c;

SomeStruct var;
SomeStruct* p = &var;
// Assigning static member variable c in struct SomeStruct.
SomeStruct::c = 5;
// Accessing static member variable c in struct SomeStruct, through var and p.
var.a = var.c;
var.b = p->c;
// Calling a static member function.
SomeStruct::bar();
var.bar();
p->bar();

Contexte

L'opérateur -> est nécessaire car l'opérateur d'accès au membre . a priorité sur l'opérateur de déréférencement * .

On pourrait s'attendre à ce que *pa déréférencement p (résultant en une référence à l'objet p pointe vers) et à accéder ensuite à son membre a . Mais en fait, il essaie d'accéder au membre a de p et le déréférencer. Ie *pa équivaut à *(pa) . Dans l'exemple ci-dessus, cela entraînerait une erreur de compilation à cause de deux faits: Premièrement, p est un pointeur et n'a pas de membre a . Deuxièmement, a est un entier et ne peut donc pas être déréférencé.

La solution peu commune à ce problème serait de contrôler explicitement la priorité: (*p).a

Au lieu de cela, l'opérateur -> est presque toujours utilisé. C'est un raccourci pour déréférencer le pointeur et y accéder. Ie (*p).a est exactement le même que p->a .

L'opérateur :: est l'opérateur de portée, utilisé de la même manière que l'accès à un membre d'un espace de noms. En effet, un membre de classe statique est considéré comme étant dans la portée de cette classe, mais n'est pas considéré comme membre des instances de cette classe. L'utilisation de la normale . et -> est également autorisé pour les membres statiques, même s'ils ne sont pas membres de l'instance, pour des raisons historiques; Ceci est utile pour écrire du code générique dans les modèles, car l'appelant n'a pas besoin de se préoccuper de savoir si une fonction membre donnée est statique ou non.

Héritage privé: restriction de l'interface de la classe de base

L'héritage privé est utile lorsqu'il est nécessaire de restreindre l'interface publique de la classe:

class A {
public:
    int move();
    int turn();
};

class B : private A {
public:
    using A::turn; 
};

B b;
b.move();  // compile error
b.turn();  // OK

Cette approche empêche efficacement l'accès aux méthodes publiques A en lançant sur le pointeur ou la référence A:

B b; 
A& a = static_cast<A&>(b); // compile error

Dans le cas de l'héritage public, un tel casting donnera accès à toutes les méthodes publiques A, bien qu'il existe d'autres moyens d'empêcher que cela se produise dans B dérivé, comme cacher:

class B : public A {
private:
    int move();  
};

ou privé en utilisant:

class B : public A {
private:
    using A::move;  
};

alors pour les deux cas, il est possible:

B b;
A& a = static_cast<A&>(b); // OK for public inheritance
a.move(); // OK

Classes finales et structures

C ++ 11

Dériver une classe peut être interdit avec le spécificateur final . Déclarons une classe finale:

class A final {
};

Maintenant, toute tentative de sous-classe provoquera une erreur de compilation:

// Compilation error: cannot derive from final class:
class B : public A {
};

La classe finale peut apparaître n'importe où dans la hiérarchie des classes:

class A {
};

// OK.
class B final : public A {
};

// Compilation error: cannot derive from final class B.
class C : public B {
};

Relation amicale

Le mot - clé friend permet aux autres classes et fonctions d'accéder aux membres privés et protégés de la classe, même s'ils sont définis en dehors de la portée de la classe.

class Animal{
private:
    double weight;
    double height;
public:
    friend void printWeight(Animal animal);
    friend class AnimalPrinter;
    // A common use for a friend function is to overload the operator<< for streaming. 
    friend std::ostream& operator<<(std::ostream& os, Animal animal);
};

void printWeight(Animal animal)
{
    std::cout << animal.weight << "\n";
}

class AnimalPrinter
{
public:
    void print(const Animal& animal)
    {
        // Because of the `friend class AnimalPrinter;" declaration, we are
        // allowed to access private members here.
        std::cout << animal.weight << ", " << animal.height << std::endl;
    }
}

std::ostream& operator<<(std::ostream& os, Animal animal)
{
    os << "Animal height: " << animal.height << "\n";
    return os;
}

int main() {
    Animal animal = {10, 5};
    printWeight(animal);

    AnimalPrinter aPrinter;
    aPrinter.print(animal);

    std::cout << animal;
}

10
10, 5
Animal height: 5

Classes / Structures imbriquées

Une class ou une struct peut également contenir une autre définition de class / struct , appelée "classe imbriquée"; dans cette situation, la classe contenant est appelée "classe englobante". La définition de classe imbriquée est considérée comme un membre de la classe englobante, mais est par ailleurs distincte.

struct Outer {
    struct Inner { };
};

En dehors de la classe englobante, les classes imbriquées sont accessibles à l'aide de l'opérateur scope. A l'intérieur de la classe englobante, cependant, les classes imbriquées peuvent être utilisées sans qualificatifs:

struct Outer {
    struct Inner { };

    Inner in;
};

// ...

Outer o;
Outer::Inner i = o.in;

Comme pour une class / struct non imbriquée, les fonctions membres et les variables statiques peuvent être définies dans une classe imbriquée ou dans l'espace de noms englobant. Cependant, ils ne peuvent pas être définis dans la classe englobante, car ils sont considérés comme une classe différente de la classe imbriquée.

// Bad.
struct Outer {
    struct Inner {
        void do_something();
    };

    void Inner::do_something() {}
};


// Good.
struct Outer {
    struct Inner {
        void do_something();
    };

};

void Outer::Inner::do_something() {}

Comme pour les classes non imbriquées, les classes imbriquées peuvent être déclarées et définies ultérieurement, à condition qu'elles soient définies avant d'être utilisées directement.

class Outer {
    class Inner1;
    class Inner2;

    class Inner1 {};

    Inner1 in1;
    Inner2* in2p;

  public:
    Outer();
    ~Outer();
};

class Outer::Inner2 {};

Outer::Outer() : in1(Inner1()), in2p(new Inner2) {}
Outer::~Outer() {
    if (in2p) { delete in2p; }
}

C ++ 11

Avant C ++ 11, les classes imbriquées n'avaient accès qu'aux noms de type, static membres static et aux énumérateurs de la classe englobante; tous les autres membres définis dans la classe englobante étaient hors limites.

C ++ 11

A partir de C ++ 11, les classes imbriquées et leurs membres sont traités comme s'ils étaient des friend de la classe englobante et peuvent accéder à tous ses membres, conformément aux règles d'accès habituelles; Si les membres de la classe imbriquée doivent pouvoir évaluer un ou plusieurs membres non statiques de la classe englobante, ils doivent donc passer une instance:

class Outer {
    struct Inner {
        int get_sizeof_x() {
            return sizeof(x); // Legal (C++11): x is unevaluated, so no instance is required.
        }

        int get_x() {
            return x; // Illegal: Can't access non-static member without an instance.
        }

        int get_x(Outer& o) {
            return o.x; // Legal (C++11): As a member of Outer, Inner can access private members.
        }
    };

    int x;
};

À l'inverse, la classe englobante n'est pas traitée comme une amie de la classe imbriquée et ne peut donc pas accéder à ses membres privés sans obtenir explicitement l'autorisation.

class Outer {
    class Inner {
        // friend class Outer;

        int x;
    };

    Inner in;

  public:
    int get_x() {
        return in.x; // Error: int Outer::Inner::x is private.
        // Uncomment "friend" line above to fix.
    }
};

Les amis d'une classe imbriquée ne sont pas automatiquement considérés comme des amis de la classe englobante; S'ils doivent également être amis de la classe fermée, cela doit être déclaré séparément. Inversement, comme la classe englobante n'est pas automatiquement considérée comme une amie de la classe imbriquée, les amis de la classe englobante ne seront pas non plus considérés comme des amis de la classe imbriquée.

class Outer {
    friend void barge_out(Outer& out, Inner& in);

    class Inner {
        friend void barge_in(Outer& out, Inner& in);

        int i;
    };

    int o;
};

void barge_in(Outer& out, Outer::Inner& in) {
    int i = in.i;  // Good.
    int o = out.o; // Error: int Outer::o is private.
}

void barge_out(Outer& out, Outer::Inner& in) {
    int i = in.i;  // Error: int Outer::Inner::i is private.
    int o = out.o; // Good.
}

Comme avec tous les autres membres de la classe, les classes imbriquées ne peuvent être nommées qu'en dehors de la classe si elles ont un accès public. Cependant, vous êtes autorisé à y accéder indépendamment du modificateur d'accès, tant que vous ne les nommez pas explicitement.

class Outer {
    struct Inner {
        void func() { std::cout << "I have no private taboo.\n"; }
    };

  public:
    static Inner make_Inner() { return Inner(); }
};

// ...

Outer::Inner oi; // Error: Outer::Inner is private.

auto oi = Outer::make_Inner(); // Good.
oi.func();                     // Good.
Outer::make_Inner().func();    // Good.

Vous pouvez également créer un alias de type pour une classe imbriquée. Si un alias de type est contenu dans la classe englobante, le type imbriqué et l'alias de type peuvent avoir des modificateurs d'accès différents. Si l'alias de type est en dehors de la classe englobante, il faut que la classe imbriquée, ou un typedef correspondant, soit publique.

class Outer {
    class Inner_ {};

  public:
    typedef Inner_ Inner;
};

typedef Outer::Inner  ImOut; // Good.
typedef Outer::Inner_ ImBad; // Error.

// ...

Outer::Inner  oi; // Good.
Outer::Inner_ oi; // Error.
ImOut         oi; // Good.

Comme pour les autres classes, les classes imbriquées peuvent être dérivées ou dérivées d'autres classes.

struct Base {};

struct Outer {
    struct Inner : Base {};
};

struct Derived : Outer::Inner {};

Cela peut être utile dans les situations où la classe englobante est dérivée d'une autre classe, en permettant au programmeur de mettre à jour la classe imbriquée si nécessaire. Cela peut être combiné avec un typedef pour fournir un nom cohérent pour chaque classe imbriquée:

class BaseOuter {
    struct BaseInner_ {
        virtual void do_something() {}
        virtual void do_something_else();
    } b_in;

  public:
    typedef BaseInner_ Inner;

    virtual ~BaseOuter() = default;

    virtual Inner& getInner() { return b_in; }
};

void BaseOuter::BaseInner_::do_something_else() {}

// ---

class DerivedOuter : public BaseOuter {
    // Note the use of the qualified typedef; BaseOuter::BaseInner_ is private.
    struct DerivedInner_ : BaseOuter::Inner {
        void do_something() override {}
        void do_something_else() override;
    } d_in;

  public:
    typedef DerivedInner_ Inner;

    BaseOuter::Inner& getInner() override { return d_in; }
};

void DerivedOuter::DerivedInner_::do_something_else() {}

// ...

// Calls BaseOuter::BaseInner_::do_something();
BaseOuter* b = new BaseOuter;
BaseOuter::Inner& bin = b->getInner();
bin.do_something();
b->getInner().do_something();

// Calls DerivedOuter::DerivedInner_::do_something();
BaseOuter* d = new DerivedOuter;
BaseOuter::Inner& din = d->getInner();
din.do_something();
d->getInner().do_something();

Dans le cas ci-dessus, BaseOuter et DerivedOuter fournissent respectivement le type de membre Inner , BaseInner_ et DerivedInner_ . Cela permet de dériver des types imbriqués sans casser l'interface de la classe englobante, et permet d'utiliser le type imbriqué de manière polymorphe.

Types de membres et alias

Une class ou une struct peut également définir des alias de type de membre, qui sont des alias de type contenus dans la classe et traités comme tels.

struct IHaveATypedef {
    typedef int MyTypedef;
};

struct IHaveATemplateTypedef {
    template<typename T>
    using MyTemplateTypedef = std::vector<T>;
};

Comme les membres statiques, ces typedefs sont accessibles à l’aide de l’opérateur scope, :: .

IHaveATypedef::MyTypedef i = 5; // i is an int.

IHaveATemplateTypedef::MyTemplateTypedef<int> v; // v is a std::vector<int>.

Comme pour les alias de type normal, chaque alias de type membre est autorisé à faire référence à tout type défini ou alias avant, mais pas après sa définition. De même, un typedef en dehors de la définition de la classe peut faire référence à tous les typedefs accessibles dans la définition de la classe, à condition qu'il vienne après la définition de la classe.

template<typename T>
struct Helper {
    T get() const { return static_cast<T>(42); }
};

struct IHaveTypedefs {
//    typedef MyTypedef NonLinearTypedef; // Error if uncommented.
    typedef int MyTypedef;
    typedef Helper<MyTypedef> MyTypedefHelper;
};

IHaveTypedefs::MyTypedef        i; // x_i is an int.
IHaveTypedefs::MyTypedefHelper hi; // x_hi is a Helper<int>.

typedef IHaveTypedefs::MyTypedef TypedefBeFree;
TypedefBeFree ii;                  // ii is an int.

Les alias de type de membre peuvent être déclarés avec n'importe quel niveau d'accès et respecteront le modificateur d'accès approprié.

class TypedefAccessLevels {
    typedef int PrvInt;

  protected:
    typedef int ProInt;

  public:
    typedef int PubInt;
};

TypedefAccessLevels::PrvInt prv_i; // Error: TypedefAccessLevels::PrvInt is private.
TypedefAccessLevels::ProInt pro_i; // Error: TypedefAccessLevels::ProInt is protected.
TypedefAccessLevels::PubInt pub_i; // Good.

class Derived : public TypedefAccessLevels {
    PrvInt prv_i; // Error: TypedefAccessLevels::PrvInt is private.
    ProInt pro_i; // Good.
    PubInt pub_i; // Good.
};

Cela peut être utilisé pour fournir un niveau d'abstraction, permettant au concepteur d'une classe de modifier son fonctionnement interne sans casser le code qui en dépend.

class Something {
    friend class SomeComplexType;

    short s;
    // ...

  public:
    typedef SomeComplexType MyHelper;

    MyHelper get_helper() const { return MyHelper(8, s, 19.5, "shoe", false); }

    // ...
};

// ...

Something s;
Something::MyHelper hlp = s.get_helper();

Dans ce cas, si la classe d'assistance est changée de SomeComplexType à un autre type, seules les déclarations typedef et friend devront être modifiées; tant que la classe d'assistance fournit les mêmes fonctionnalités, tout code qui l'utilise comme Something::MyHelper au lieu de le spécifier par nom fonctionnera toujours sans aucune modification. De cette manière, nous réduisons la quantité de code à modifier lorsque l’implémentation sous-jacente est modifiée, de sorte que le nom du type ne doit être modifié que dans un emplacement.

Cela peut également être combiné avec decltype , si l'on le souhaite.

class SomethingElse {
    AnotherComplexType<bool, int, SomeThirdClass> helper;

  public:
    typedef decltype(helper) MyHelper;

  private:
    InternalVariable<MyHelper> ivh;

    // ...

  public:
    MyHelper& get_helper() const { return helper; }

    // ...
};

Dans cette situation, changer l'implémentation de SomethingElse::helper changera automatiquement le typedef pour nous, en raison de decltype . Cela minimise le nombre de modifications nécessaires lorsque l'on veut changer d' helper , ce qui minimise le risque d'erreur humaine.

Comme pour tout, cependant, cela peut être pris trop loin. Si le nom de fichier n'est utilisé qu'une ou deux fois en interne et zéro fois en externe, par exemple, il n'est pas nécessaire de lui fournir un alias. S'il est utilisé des centaines ou des milliers de fois au cours d'un projet ou s'il porte un nom suffisamment long, il peut être utile de le fournir en tant que typedef au lieu de toujours l'utiliser en termes absolus. Il faut équilibrer la compatibilité et la commodité avec la quantité de bruit inutile créée.


Cela peut également être utilisé avec les classes de modèle, pour fournir un accès aux paramètres du modèle depuis l'extérieur de la classe.

template<typename T>
class SomeClass {
    // ...

  public:
    typedef T MyParam;
    MyParam getParam() { return static_cast<T>(42); }
};

template<typename T>
typename T::MyParam some_func(T& t) {
    return t.getParam();
}

SomeClass<int> si;
int i = some_func(si);

Ceci est couramment utilisé avec les conteneurs, qui fournissent généralement leur type d'élément et d'autres types d'assistance, en tant qu'alias de type de membre. La plupart des conteneurs de la bibliothèque standard C ++, par exemple, fournissent les 12 types d'assistance suivants, ainsi que tout autre type spécial dont ils pourraient avoir besoin.

template<typename T>
class SomeContainer {
    // ...

  public:
    // Let's provide the same helper types as most standard containers.
    typedef T                                     value_type;
    typedef std::allocator<value_type>            allocator_type;
    typedef value_type&                           reference;
    typedef const value_type&                     const_reference;
    typedef value_type*                           pointer;
    typedef const value_type*                     const_pointer;
    typedef MyIterator<value_type>                iterator;
    typedef MyConstIterator<value_type>           const_iterator;
    typedef std::reverse_iterator<iterator>       reverse_iterator;
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
    typedef size_t                                size_type;
    typedef ptrdiff_t                             difference_type;
};

Avant C ++ 11, il était également couramment utilisé pour fournir un "template typedef " de toutes sortes, car la fonctionnalité n'était pas encore disponible; ceux-ci sont devenus un peu moins courants avec l'introduction de modèles d'alias, mais sont toujours utiles dans certaines situations (et sont combinés avec des modèles d'alias dans d'autres situations, ce qui peut être très utile pour obtenir des composants individuels complexes comme un pointeur de fonction) ). Ils utilisent généralement le type nom pour leur alias de type.

template<typename T>
struct TemplateTypedef {
    typedef T type;
}

TemplateTypedef<int>::type i; // i is an int.

Cela a souvent été utilisé avec des types avec plusieurs paramètres de modèle, pour fournir un alias qui définit un ou plusieurs paramètres.

template<typename T, size_t SZ, size_t D>
class Array { /* ... */ };

template<typename T, size_t SZ>
struct OneDArray {
    typedef Array<T, SZ, 1> type;
};

template<typename T, size_t SZ>
struct TwoDArray {
    typedef Array<T, SZ, 2> type;
};

template<typename T>
struct MonoDisplayLine {
    typedef Array<T, 80, 1> type;
};

OneDArray<int, 3>::type     arr1i; // arr1i is an Array<int, 3, 1>.
TwoDArray<short, 5>::type   arr2s; // arr2s is an Array<short, 5, 2>.
MonoDisplayLine<char>::type arr3c; // arr3c is an Array<char, 80, 1>.

Membres de la classe statique

Une classe est également autorisée à avoir static membres static , qui peuvent être des variables ou des fonctions. Ceux-ci sont considérés comme étant dans la portée de la classe, mais ne sont pas traités comme des membres normaux; ils ont une durée de stockage statique (ils existent depuis le début du programme jusqu'à la fin), ne sont pas liés à une instance particulière de la classe et une seule copie existe pour la classe entière.

class Example {
    static int num_instances;      // Static data member (static member variable).
    int i;                         // Non-static member variable.

  public:
    static std::string static_str; // Static data member (static member variable).
    static int static_func();      // Static member function.

    // Non-static member functions can modify static member variables.
    Example() { ++num_instances; }
    void set_str(const std::string& str);
};

int         Example::num_instances;
std::string Example::static_str = "Hello.";

// ...

Example one, two, three;
// Each Example has its own "i", such that:
//  (&one.i != &two.i)
//  (&one.i != &three.i)
//  (&two.i != &three.i).
// All three Examples share "num_instances", such that:
//  (&one.num_instances == &two.num_instances)
//  (&one.num_instances == &three.num_instances)
//  (&two.num_instances == &three.num_instances)

Les variables de membre statiques ne sont pas considérées comme définies dans la classe, seulement déclarées, et ont donc leur définition en dehors de la définition de classe; le programmeur est autorisé, mais pas obligatoire, à initialiser les variables statiques dans leur définition. Lors de la définition des variables membres, le mot clé static est omis.

class Example {
    static int num_instances;               // Declaration.

  public:
    static std::string static_str;          // Declaration.

    // ...
};

int         Example::num_instances;         // Definition.  Zero-initialised.
std::string Example::static_str = "Hello."; // Definition.

De ce fait, les variables statiques peuvent être des types incomplets (sauf le void ), à condition qu'elles soient définies ultérieurement comme un type complet.

struct ForwardDeclared;

class ExIncomplete {
    static ForwardDeclared fd;
    static ExIncomplete    i_contain_myself;
    static int             an_array[];
};

struct ForwardDeclared {};

ForwardDeclared ExIncomplete::fd;
ExIncomplete    ExIncomplete::i_contain_myself;
int             ExIncomplete::an_array[5];

Les fonctions membres statiques peuvent être définies à l'intérieur ou à l'extérieur de la définition de classe, comme pour les fonctions membres normales. Comme pour les variables membres statiques, le mot clé static est omis lors de la définition de fonctions membres statiques en dehors de la définition de classe.

// For Example above, either...
class Example {
    // ...

  public:
    static int static_func() { return num_instances; }

    // ...

    void set_str(const std::string& str) { static_str = str; }
};

// Or...

class Example { /* ... */ };

int  Example::static_func() { return num_instances; }
void Example::set_str(const std::string& str) { static_str = str; }

Si une variable membre statique est déclarée const mais pas volatile et est de type intégrale ou énumération, elle peut être initialisée à la déclaration, à l'intérieur de la définition de classe.

enum E { VAL = 5 };

struct ExConst {
    const static int ci = 5;              // Good.
    static const E ce = VAL;              // Good.
    const static double cd = 5;           // Error.
    static const volatile int cvi = 5;    // Error.

    const static double good_cd;
    static const volatile int good_cvi;
};

const double ExConst::good_cd = 5;        // Good.
const volatile int ExConst::good_cvi = 5; // Good.
C ++ 11

A partir de C ++ 11, les variables membres statiques de types LiteralType (types pouvant être construits au moment de la compilation, selon les règles constexpr ) peuvent également être déclarées en tant que constexpr ; Si tel est le cas, ils doivent être initialisés dans la définition de classe.

struct ExConstexpr {
    constexpr static int ci = 5;                      // Good.
    static constexpr double cd = 5;                   // Good.
    constexpr static int carr[] = { 1, 1, 2 };        // Good.
    static constexpr ConstexprConstructibleClass c{}; // Good.
    constexpr static int bad_ci;                      // Error.
};

constexpr int ExConstexpr::bad_ci = 5;                // Still an error.

Si une variable membre statique const ou constexpr est odr-used (de manière informelle, si son adresse a été prise ou est affectée à une référence), elle doit toujours avoir une définition distincte, en dehors de la définition de classe. Cette définition n'est pas autorisée à contenir un initialiseur.

struct ExODR {
    static const int odr_used = 5;
};

// const int ExODR::odr_used;

const int* odr_user = & ExODR::odr_used; // Error; uncomment above line to resolve.

Comme les membres statiques ne sont pas liés à une instance donnée, ils peuvent être accédés en utilisant l’opérateur scope, :: .

std::string str = Example::static_str;

Il est également possible d'y accéder comme s'il s'agissait de membres normaux et non statiques. Ceci a une importance historique, mais est utilisé moins souvent que l'opérateur de la portée pour éviter toute confusion quant à savoir si un membre est statique ou non.

Example ex;
std::string rts = ex.static_str;

Les membres de classe peuvent accéder aux membres statiques sans qualifier leur portée, comme avec les membres de classe non statiques.

class ExTwo {
    static int num_instances;
    int my_num;

  public:
    ExTwo() : my_num(num_instances++) {}

    static int get_total_instances() { return num_instances; }
    int get_instance_number() const { return my_num; }
};

int ExTwo::num_instances;

Ils ne peuvent pas être mutable , et ils ne devraient pas l'être; comme ils ne sont liés à aucune instance donnée, le fait qu'une instance soit ou non constante n'affecte pas les membres statiques.

struct ExDontNeedMutable {
    int immuta;
    mutable int muta;

    static int i;

    ExDontNeedMutable() : immuta(-5), muta(-5) {}
};
int ExDontNeedMutable::i;

// ...

const ExDontNeedMutable dnm;
dnm.immuta = 5; // Error: Can't modify read-only object.
dnm.muta = 5;   // Good.  Mutable fields of const objects can be written.
dnm.i = 5;      // Good.  Static members can be written regardless of an instance's const-ness.

Les membres statiques respectent les modificateurs d'accès, tout comme les membres non statiques.

class ExAccess {
    static int prv_int;

  protected:
    static int pro_int;

  public:
    static int pub_int;
};

int ExAccess::prv_int;
int ExAccess::pro_int;
int ExAccess::pub_int;

// ...

int x1 = ExAccess::prv_int; // Error: int ExAccess::prv_int is private.
int x2 = ExAccess::pro_int; // Error: int ExAccess::pro_int is protected.
int x3 = ExAccess::pub_int; // Good.

Comme ils ne sont pas liés à une instance donnée, les fonctions membres statiques n'ont pas this pointeur; De ce fait, ils ne peuvent pas accéder aux variables membres non statiques à moins de passer une instance.

class ExInstanceRequired {
    int i;

  public:
    ExInstanceRequired() : i(0) {}

    static void bad_mutate() { ++i *= 5; }                         // Error.
    static void good_mutate(ExInstanceRequired& e) { ++e.i *= 5; } // Good.
};

En raison de l'absence de this pointeur, leurs adresses ne peuvent pas être stockées dans des fonctions de pointeur à membre et sont stockées dans des pointeurs à fonctions normaux.

struct ExPointer {
           void nsfunc() {}
    static void  sfunc() {}
};

typedef void (ExPointer::* mem_f_ptr)();
typedef void (*f_ptr)();

mem_f_ptr p_sf = &ExPointer::sfunc; // Error.
    f_ptr p_sf = &ExPointer::sfunc; // Good.

En raison de l'absence de this pointeur, ils ne peuvent pas non plus être const ou volatile , ni avoir de qualificatifs ref. Ils ne peuvent pas non plus être virtuels.

struct ExCVQualifiersAndVirtual {
    static void   func()                {} // Good.
    static void  cfunc() const          {} // Error.
    static void  vfunc() volatile       {} // Error.
    static void cvfunc() const volatile {} // Error.
    static void  rfunc() &              {} // Error.
    static void rvfunc() &&             {} // Error.

    virtual static void vsfunc()        {} // Error.
    static virtual void svfunc()        {} // Error.
};

Comme elles ne sont pas liées à une instance donnée, les variables de membre statiques sont traitées comme des variables globales spéciales. ils sont créés au démarrage du programme et détruits à la fermeture, que des instances de la classe existent ou non. Une seule copie de chaque variable membre statique existe (à moins que la variable soit déclarée thread_local (C ++ 11 ou version ultérieure), auquel cas il y a une copie par thread).

Les variables membres statiques ont le même lien que la classe, que la classe ait un lien externe ou interne. Les classes locales et les classes non nommées ne sont pas autorisées à avoir des membres statiques.

Fonctions de membre non statiques

Une classe peut avoir des fonctions membres non statiques qui opèrent sur des instances individuelles de la classe.

class CL {
  public:
    void member_function() {}
};

Ces fonctions sont appelées sur une instance de la classe, comme ceci:

CL instance;
instance.member_function();

Ils peuvent être définis à l'intérieur ou à l'extérieur de la définition de classe; Si elles sont définies à l'extérieur, elles sont spécifiées comme étant dans l'étendue de la classe.

struct ST {
    void  defined_inside() {}
    void defined_outside();
};
void ST::defined_outside() {}

Ils peuvent être qualifiés de CV et / ou ref-qualifiés , ce qui affecte la façon dont ils voient l'instance sur laquelle ils sont appelés; la fonction verra l'instance comme ayant le ou les qualificatifs cv spécifiés, le cas échéant. La version appelée sera basée sur les qualificatifs cv de l'instance. S'il n'y a pas de version avec les mêmes qualificatifs cv que l'instance, une version qualifiée plus-cv sera appelée si disponible.

struct CVQualifiers {
    void func()                   {} // 1: Instance is non-cv-qualified.
    void func() const             {} // 2: Instance is const.

    void cv_only() const volatile {}
};

CVQualifiers       non_cv_instance;
const CVQualifiers      c_instance;

non_cv_instance.func(); // Calls #1.
c_instance.func();      // Calls #2.

non_cv_instance.cv_only(); // Calls const volatile version.
c_instance.cv_only();      // Calls const volatile version.
C ++ 11

Les qualificateurs de référence de la fonction membre indiquent si la fonction est destinée à être appelée sur les instances rvalue et utilisent la même syntaxe que la fonction cv-qualifiers.

struct RefQualifiers {
    void func() &  {} // 1: Called on normal instances.
    void func() && {} // 2: Called on rvalue (temporary) instances.
};

RefQualifiers rf;
rf.func();              // Calls #1.
RefQualifiers{}.func(); // Calls #2.

Les qualificateurs de CV et les qualificatifs de référence peuvent également être combinés si nécessaire.

struct BothCVAndRef {
    void func() const& {} // Called on normal instances.  Sees instance as const.
    void func() &&     {} // Called on temporary instances.
};

Ils peuvent aussi être virtuels ; Ceci est fondamental pour le polymorphisme et permet à une ou plusieurs classes enfant de fournir la même interface que la classe parente, tout en fournissant leurs propres fonctionnalités.

struct Base {
    virtual void func() {}
};
struct Derived {
    virtual void func() {}
};

Base* bp = new Base;
Base* dp = new Derived;
bp.func(); // Calls Base::func().
dp.func(); // Calls Derived::func().

Pour plus d'informations, voir ici .

Structure / classe sans nom

Un struct sans nom est autorisé (le type n'a pas de nom)

void foo()
{
    struct /* No name */ {
        float x;
        float y;
    } point;
    
    point.x = 42;
}

ou

struct Circle
{
    struct /* No name */ {
        float x;
        float y;
    } center; // but a member name
    float radius;
};

et ensuite

Circle circle;
circle.center.x = 42.f;

mais non anonyme struct (type sans nom et objet sans nom)

struct InvalidCircle
{
    struct /* No name */ {
        float centerX;
        float centerY;
    }; // No member either.
    float radius;
};

Remarque: Certains compilateurs autorisent la struct anonyme en tant qu’extension .

C ++ 11
  • lamdba peut être vu comme une struct spéciale non nommée .

  • decltype permet de récupérer le type de struct sans nom :

    decltype(circle.point) otherPoint;
    
  • Une instance de struct sans nom peut être un paramètre de la méthode template:

    void print_square_coordinates()
    {
        const struct {float x; float y;} points[] = {
            {-1, -1}, {-1, 1}, {1, -1}, {1, 1}
        };
    
        // for range relies on `template <class T, std::size_t N> std::begin(T (&)[N])`
        for (const auto& point : points) { 
            std::cout << "{" << point.x << ", " << point.y << "}\n";
        }
    
        decltype(points[0]) topRightCorner{1, 1};
        auto it = std::find(points, points + 4, topRightCorner);
        std::cout << "top right corner is the "
                  << 1 + std::distance(points, it) << "th\n";
    }
    


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow