C++
Fonctions membres non statiques
Recherche…
Syntaxe
// appelant:
- variable.member_function ();
- variable_pointer-> member_function ();
// Définition:
- ret_type class_name :: member_function () cv-qualifiers {
- corps;
- }
- ret_type class_name :: member_function () cv-qualifiers {
// Prototype:
- class class_name {
- spécificateur virtuel ret_type member_function () cv-qualifiers virt-specifier-seq;
- // virt-specifier: "virtuel", le cas échéant.
- // qualificatifs-cv: "const" et / ou "volatile", le cas échéant.
- // virt-specifier-seq: "override" et / ou "final", le cas échéant.
- }
- class class_name {
Remarques
Une fonction membre non static
est une fonction membre class
/ struct
/ union
, appelée sur une instance particulière et opérant sur cette instance. Contrairement static
fonctions membres static
, il ne peut pas être appelé sans spécifier d'instance.
Pour plus d'informations sur les classes, les structures et les unions, consultez le sujet parent .
Fonctions de membres non statiques
Une class
ou une struct
peut avoir des fonctions membres ainsi que des variables membres. Ces fonctions ont une syntaxe similaire à celle des fonctions autonomes et peuvent être définies à l'intérieur ou à l'extérieur de la définition de la classe. S'il est défini en dehors de la définition de la classe, le nom de la fonction est précédé du nom de la classe et de l'opérateur scope ( ::
:).
class CL {
public:
void definedInside() {}
void definedOutside();
};
void CL::definedOutside() {}
Ces fonctions sont appelées sur une instance (ou référence à une instance) de la classe avec l'opérateur point ( .
), Ou un pointeur sur une instance avec l'opérateur arrow ( ->
), et chaque appel est lié à l'instance de la fonction a été appelé; Lorsqu'une fonction membre est appelée sur une instance, elle a accès à tous les champs de cette instance (via this
pointeur ), mais ne peut accéder qu'aux champs des autres instances si ces instances sont fournies en tant que paramètres.
struct ST {
ST(const std::string& ss = "Wolf", int ii = 359) : s(ss), i(ii) { }
int get_i() const { return i; }
bool compare_i(const ST& other) const { return (i == other.i); }
private:
std::string s;
int i;
};
ST st1;
ST st2("Species", 8472);
int i = st1.get_i(); // Can access st1.i, but not st2.i.
bool b = st1.compare_i(st2); // Can access st1 & st2.
Ces fonctions sont autorisées à accéder aux variables membres et / ou à d'autres fonctions membres, indépendamment des modificateurs d'accès de la variable ou de la fonction. Ils peuvent également être écrits dans le désordre, accéder aux variables membres et / ou aux fonctions membres appelantes déclarées avant eux, car la définition de classe entière doit être analysée avant que le compilateur puisse commencer à compiler une classe.
class Access {
public:
Access(int i_ = 8088, int j_ = 8086, int k_ = 6502) : i(i_), j(j_), k(k_) {}
int i;
int get_k() const { return k; }
bool private_no_more() const { return i_be_private(); }
protected:
int j;
int get_i() const { return i; }
private:
int k;
int get_j() const { return j; }
bool i_be_private() const { return ((i > j) && (k < j)); }
};
Encapsulation
Une utilisation courante des fonctions membres est l’encapsulation, utilisant un accesseur (communément appelé getter) et un mutateur (communément appelé setter) au lieu d’accéder directement aux champs.
class Encapsulator {
int encapsulated;
public:
int get_encapsulated() const { return encapsulated; }
void set_encapsulated(int e) { encapsulated = e; }
void some_func() {
do_something_with(encapsulated);
}
};
À l'intérieur de la classe, n'importe quelle fonction membre non statique peut accéder librement à l' encapsulated
. en dehors de la classe, l'accès à celle-ci est régulé par les fonctions membres, en utilisant get_encapsulated()
pour le lire et set_encapsulated()
pour le modifier. Cela évite les modifications involontaires de la variable, car des fonctions distinctes sont utilisées pour la lire et l'écrire. [Il y a beaucoup de discussions pour savoir si les getters et les installateurs fournissent ou cassent l'encapsulation, avec de bons arguments pour les deux revendications; un tel débat est hors de portée de cet exemple.]
Nom Cacher et importer
Lorsqu'une classe de base fournit un ensemble de fonctions surchargées et qu'une classe dérivée ajoute une autre surcharge à l'ensemble, cela masque toutes les surcharges fournies par la classe de base.
struct HiddenBase {
void f(int) { std::cout << "int" << std::endl; }
void f(bool) { std::cout << "bool" << std::endl; }
void f(std::string) { std::cout << "std::string" << std::endl; }
};
struct HidingDerived : HiddenBase {
void f(float) { std::cout << "float" << std::endl; }
};
// ...
HiddenBase hb;
HidingDerived hd;
std::string s;
hb.f(1); // Output: int
hb.f(true); // Output: bool
hb.f(s); // Output: std::string;
hd.f(1.f); // Output: float
hd.f(3); // Output: float
hd.f(true); // Output: float
hd.f(s); // Error: Can't convert from std::string to float.
Ceci est dû aux règles de résolution des noms: Lors de la recherche de nom, une fois le nom correct trouvé, nous ne cherchons plus, même si nous n'avons clairement pas trouvé la version correcte de l'entité avec ce nom (avec hd.f(s)
par hd.f(s)
); De ce fait, la surcharge de la fonction dans la classe dérivée empêche la recherche de nom de découvrir les surcharges dans la classe de base. Pour éviter cela, une déclaration d'utilisation peut être utilisée pour "importer" des noms de la classe de base dans la classe dérivée, afin qu'ils soient disponibles lors de la recherche de nom.
struct HidingDerived : HiddenBase {
// All members named HiddenBase::f shall be considered members of HidingDerived for lookup.
using HiddenBase::f;
void f(float) { std::cout << "float" << std::endl; }
};
// ...
HidingDerived hd;
hd.f(1.f); // Output: float
hd.f(3); // Output: int
hd.f(true); // Output: bool
hd.f(s); // Output: std::string
Si une classe dérivée importe des noms avec une déclaration using, mais déclare également des fonctions avec la même signature que les fonctions de la classe de base, les fonctions de classe de base seront ignorées ou masquées.
struct NamesHidden {
virtual void hide_me() {}
virtual void hide_me(float) {}
void hide_me(int) {}
void hide_me(bool) {}
};
struct NameHider : NamesHidden {
using NamesHidden::hide_me;
void hide_me() {} // Overrides NamesHidden::hide_me().
void hide_me(int) {} // Hides NamesHidden::hide_me(int).
};
Une déclaration using peut également être utilisée pour modifier les modificateurs d'accès, à condition que l'entité importée soit public
ou protected
dans la classe de base.
struct ProMem {
protected:
void func() {}
};
struct BecomesPub : ProMem {
using ProMem::func;
};
// ...
ProMem pm;
BecomesPub bp;
pm.func(); // Error: protected.
bp.func(); // Good.
De même, si nous voulons explicitement appeler une fonction membre à partir d'une classe spécifique dans la hiérarchie d'héritage, nous pouvons qualifier le nom de la fonction lors de l'appel de la fonction, en spécifiant cette classe par son nom.
struct One {
virtual void f() { std::cout << "One." << std::endl; }
};
struct Two : One {
void f() override {
One::f(); // this->One::f();
std::cout << "Two." << std::endl;
}
};
struct Three : Two {
void f() override {
Two::f(); // this->Two::f();
std::cout << "Three." << std::endl;
}
};
// ...
Three t;
t.f(); // Normal syntax.
t.Two::f(); // Calls version of f() defined in Two.
t.One::f(); // Calls version of f() defined in One.
Fonctions membres virtuelles
Les fonctions membres peuvent également être déclarées virtual
. Dans ce cas, si elles sont appelées sur un pointeur ou une référence à une instance, elles ne seront pas directement accessibles. ils rechercheront plutôt la fonction dans la table des fonctions virtuelles (une liste de fonctions pointeur vers membre pour les fonctions virtuelles, plus communément appelée vtable
ou vftable
), et l'utiliseront pour appeler la version adaptée à la dynamique de l'instance. type (réel) Si la fonction est appelée directement, à partir d'une variable d'une classe, aucune recherche n'est effectuée.
struct Base {
virtual void func() { std::cout << "In Base." << std::endl; }
};
struct Derived : Base {
void func() override { std::cout << "In Derived." << std::endl; }
};
void slicer(Base x) { x.func(); }
// ...
Base b;
Derived d;
Base *pb = &b, *pd = &d; // Pointers.
Base &rb = b, &rd = d; // References.
b.func(); // Output: In Base.
d.func(); // Output: In Derived.
pb->func(); // Output: In Base.
pd->func(); // Output: In Derived.
rb.func(); // Output: In Base.
rd.func(); // Output: In Derived.
slicer(b); // Output: In Base.
slicer(d); // Output: In Base.
Notez que si pd
est Base*
et que rd
est une Base&
, appeler func()
sur l'un des deux appels Derived::func()
au lieu de Base::func()
; C'est parce que la vtable
pour Derived
met à jour l'entrée Base::func()
pour indiquer plutôt Derived::func()
. À l'inverse, notez que le fait de passer une instance à slicer()
entraîne toujours l'appel de Base::func()
, même si l'instance transmise est un Derived
. Cela est dû à quelque chose appelé découpage de données , où le fait de passer une instance Derived
dans un paramètre Base
par valeur rend la partie de l'instance Derived
qui n'est pas une instance de Base
inaccessible.
Lorsqu'une fonction membre est définie comme étant virtuelle, toutes les fonctions membres de classe dérivées ayant la même signature la remplacent, que la fonction de substitution soit spécifiée comme virtual
ou non. Cela peut rendre les classes dérivées plus difficiles à analyser pour les programmeurs, car il n'y a aucune indication sur la ou les fonctions virtual
.
struct B {
virtual void f() {}
};
struct D : B {
void f() {} // Implicitly virtual, overrides B::f.
// You'd have to check B to know that, though.
};
Notez cependant qu'une fonction dérivée ne remplace une fonction de base que si leurs signatures correspondent; même si une fonction dérivée est explicitement déclarée virtual
, elle créera à la place une nouvelle fonction virtuelle si les signatures ne correspondent pas.
struct BadB {
virtual void f() {}
};
struct BadD : BadB {
virtual void f(int i) {} // Does NOT override BadB::f.
};
A partir de C ++ 11, l'intention de remplacer peut être explicite avec le override
mot clé contextuel. Cela indique au compilateur que le programmeur s'attend à ce qu'il remplace une fonction de classe de base, ce qui oblige le compilateur à omettre une erreur si elle ne remplace rien.
struct CPP11B {
virtual void f() {}
};
struct CPP11D : CPP11B {
void f() override {}
void f(int i) override {} // Error: Doesn't actually override anything.
};
Cela a également l'avantage de dire aux programmeurs que la fonction est à la fois virtuelle et déclarée dans au moins une classe de base, ce qui peut faciliter l'analyse des classes complexes.
Lorsqu'une fonction est déclarée virtual
et définie en dehors de la définition de la classe, le spécificateur virtual
doit être inclus dans la déclaration de la fonction et non répété dans la définition.
Cela est également vrai pour le override
.
struct VB {
virtual void f(); // "virtual" goes here.
void g();
};
/* virtual */ void VB::f() {} // Not here.
virtual void VB::g() {} // Error.
Si une classe de base surcharge une fonction virtual
, seules les surcharges explicitement spécifiées comme virtual
seront virtuelles.
struct BOverload {
virtual void func() {}
void func(int) {}
};
struct DOverload : BOverload {
void func() override {}
void func(int) {}
};
// ...
BOverload* bo = new DOverload;
bo->func(); // Calls DOverload::func().
bo->func(1); // Calls BOverload::func(int).
Pour plus d'informations, voir le sujet pertinent .
Correct Correct
L'une des principales utilisations de this
qualificateurs cv est la correction de const
. C'est la pratique de garantir que seuls les accès ayant besoin de modifier un objet peuvent modifier l'objet, et que toute fonction (membre ou non-membre) n'ayant pas besoin de modifier un objet n'a pas accès en écriture à cet objet. objet (directement ou indirectement). Cela empêche les modifications involontaires, rendant le code moins erreurprone. Il permet également à toute fonction qui n'a pas besoin de modifier l'état de prendre un objet const
ou non const
, sans avoir à réécrire ou à surcharger la fonction.
const
correction de const
, de par sa nature, commence par le bas: toute fonction de classe qui n'a pas besoin de changer d'état est déclarée comme const
, de sorte qu'elle puisse être appelée sur des instances const
. Ceci, à son tour, permet aux paramètres passés par référence d'être déclarés const
quand ils n'ont pas besoin d'être modifiés, ce qui permet aux fonctions de prendre des objets const
ou non const
sans se plaindre, et const
-ness peut se propager vers l'extérieur manière. De ce fait, les getters sont souvent const
, de même que toute autre fonction qui n'a pas besoin de modifier l'état logique.
class ConstIncorrect {
Field fld;
public:
ConstIncorrect(const Field& f) : fld(f) {} // Modifies.
const Field& get_field() { return fld; } // Doesn't modify; should be const.
void set_field(const Field& f) { fld = f; } // Modifies.
void do_something(int i) { // Modifies.
fld.insert_value(i);
}
void do_nothing() { } // Doesn't modify; should be const.
};
class ConstCorrect {
Field fld;
public:
ConstCorrect(const Field& f) : fld(f) {} // Not const: Modifies.
const Field& get_field() const { return fld; } // const: Doesn't modify.
void set_field(const Field& f) { fld = f; } // Not const: Modifies.
void do_something(int i) { // Not const: Modifies.
fld.insert_value(i);
}
void do_nothing() const { } // const: Doesn't modify.
};
// ...
const ConstIncorrect i_cant_do_anything(make_me_a_field());
// Now, let's read it...
Field f = i_cant_do_anything.get_field();
// Error: Loses cv-qualifiers, get_field() isn't const.
i_cant_do_anything.do_nothing();
// Error: Same as above.
// Oops.
const ConstCorrect but_i_can(make_me_a_field());
// Now, let's read it...
Field f = but_i_can.get_field(); // Good.
but_i_can.do_nothing(); // Good.
Comme illustré par les commentaires sur ConstIncorrect
et ConstCorrect
, les fonctions qualifiant cv servent également de documentation. Si une classe est const
correcte, une fonction qui ne const
peut supposer sans risque de changer d' état, et toute fonction qui est const
peut supposer sans risque de ne pas changer d' état.