L'initialisation des membres constants, des références et des membres dont la classe n'a pas de constructeur par défaut nécessite l'utilisation d'une liste d'initialisation. Considérons une classe Configuration illustrant ce principe.
#include <iostream>
#include <string>
class SourceDonnees {
public:
SourceDonnees(int id) : identifiant(id) {}
int obtenirId() const { return identifiant; }
private:
int identifiant;
};
class Configuration {
private:
const int version;
int& compteurRef;
SourceDonnees source;
std::string nom;
public:
// Constructeur utilisant une liste d'initialisation
Configuration(int ver, int& compteur, int srcId, std::string n)
: version(ver), compteurRef(compteur), source(srcId), nom(std::move(n)) {
// Le corps du constructeur peut contenir d'autres logiques
}
void afficher() const {
std::cout << "Version: " << version
<< ", Compteur: " << compteurRef
<< ", Source ID: " << source.obtenirId()
<< ", Nom: " << nom << std::endl;
}
};
int main() {
int compteurGlobal = 5;
Configuration cfg(2, compteurGlobal, 101, "Production");
cfg.afficher();
return 0;
}
La liste d'initialisation initialise les membres dans l'ordre de leur déclaration dans la classe, pas selon leur ordre dans la liste. Cela peut avoir des conséquences surprenantes.
class Demonstration {
private:
int alpha;
int beta;
public:
Demonstration(int val) : beta(val), alpha(beta) {}
// alpha est initialisé avant beta selon l'ordre de déclaration.
// alpha(beta) utilise donc une valeur non initialisée de beta.
void afficher() { std::cout << alpha << " " << beta << std::endl; }
};
Pour éviter de tels problèmes, il est recommandé d'aligner l'ordre dans la liste d'initialisation sur l'ordre de déclaration des membres.
Les membres static appartiennent à la classe elle-même, et non à une instance spécifique. Un membre de données static est partagé par tous les objets de la classe.
#include <iostream>
class CompteurObjets {
public:
static int instances;
CompteurObjets() { ++instances; }
~CompteurObjets() { --instances; }
};
int CompteurObjets::instances = 0;
int main() {
CompteurObjets a, b;
std::cout << "Instances actives: " << CompteurObjets::instances << std::endl; // 2
{
CompteurObjets c;
std::cout << "Dans le bloc: " << CompteurObjets::instances << std::endl; // 3
}
std::cout << "Après le bloc: " << CompteurObjets::instances << std::endl; // 2
return 0;
}
Une fonction membre static n'a pas de pointeur this implicite. Elle ne peut donc accéder qu'à d'autres membres static de la classe, pas aux membres non static qui nécessitent une instance.
class UtilitaireStatique {
private:
int donnee;
static int compteur;
public:
static void incrementerCompteur() {
// Pas d'accès à 'donnee' ici, seulement à 'compteur'.
++compteur;
}
static int obtenirCompteur() { return compteur; }
};
int UtilitaireStatique::compteur = 0;
Le mot-clé explicit appliqué à un constructeur empêche les conversions implicites. Cela évite des opérations de construction non intentionnelles et améliore la lisibilité.
class Metre {
public:
explicit Metre(double valeur) : longueur(valeur) {}
double obtenirLongueur() const { return longueur; }
private:
double longueur;
};
void afficherLongueur(const Metre& m) {
std::cout << m.obtenirLongueur() << " m" << std::endl;
}
int main() {
// afficherLongueur(5.0); // Erreur de compilation: conversion implicite interdite.
afficherLongueur(Metre(5.0)); // OK: conversion explicite.
return 0;
}
Un objet anonyme est un objet temporaire créé sans nom. Il est utile pour des opérations ponctuelles, comme passer un argument à une fonction ou initailiser un conetneur.
#include <vector>
#include <algorithm>
class Element {
public:
Element(int v) : valeur(v) {}
int obtenirValeur() const { return valeur; }
private:
int valeur;
};
int main() {
std::vector<Element> liste;
liste.push_back(Element(10)); // Objet anonyme pour l'insertion.
// Tri utilisant une lambda, qui pourrait aussi créer des objets temporaires.
std::sort(liste.begin(), liste.end(), [](const Element& a, const Element& b) {
return a.obtenirValeur() < b.obtenirValeur();
});
return 0;
}
La relation d'amitié (friend) permet à une fonction ou classe externe d'accéder aux membres privés et protégés d'une autre classe. Cela viole l'encapsulasion et augmente le couplage, son utilisation doit donc être réfléchie.
class Recepteur {
private:
int informationSecrete;
public:
Recepteur(int info) : informationSecrete(info) {}
// Déclare une fonction amie
friend void dechiffrer(const Recepteur& r);
};
void dechiffrer(const Recepteur& r) {
// Accès autorisé au membre privé
std::cout << "Info: " << r.informationSecrete << std::endl;
}
// Déclaration anticipée nécessaire
class TableauBord;
class Vehicule {
private:
int vitesse;
public:
Vehicule(int v) : vitesse(v) {}
// Déclare une classe amie
friend class TableauBord;
};
class TableauBord {
public:
void afficher(const Vehicule& v) {
// Accès autorisé au membre privé de Vehicule
std::cout << "Vitesse: " << v.vitesse << std::endl;
}
};
Une classe interne (ou classe imbriquée) est définie à l'intérieur d'une autre classe. Elle est naturellement amie de la classe englobante et peut accéder à tous ses membres, y compris les privés. L'accès inverse n'est pas automatique.
class Conteneur {
private:
int donnees;
public:
Conteneur(int d) : donnees(d) {}
class Iterateur {
public:
void analyser(const Conteneur& c) {
// Accès autorisé au membre privé 'donnees'
std::cout << "Analyse de: " << c.donnees << std::endl;
}
};
};
int main() {
Conteneur::Iterateur it;
Conteneur cont(42);
it.analyser(cont); // Fonctionne grâce à l'amitié implicite.
return 0;
}