Classes, structs et encapsulation
Comprenez la différence entre struct et class, les spécificateurs d'accès, l'encapsulation, les fonctions membres const, les membres statiques et l'initialisation d'agrégat moderne.
Qu'est-ce qu'une classe ?
Une classe en C++ est un type défini par l'utilisateur qui regroupe des données (variables membres) et des opérations sur ces données (fonctions membres) en une seule unité. Ce regroupement s'appelle l'encapsulation — l'un des quatre piliers de la POO.
L'idée clé est qu'une classe définit un invariant : un ensemble de règles qui doivent toujours être vraies concernant son état interne. Par exemple, une classe Date pourrait exiger que le mois soit toujours entre 1 et 12. L'encapsulation vous permet d'appliquer ces invariants en contrôlant l'accès à l'état interne.
struct vs class
En C++, struct et class sont presque identiques. La seule différence est le spécificateur d'accès par défaut : struct est public par défaut, tandis que class est private par défaut. Convention : utilisez struct pour les agrégats de données simples, class quand vous avez des invariants à maintenir.
#include <iostream>
#include <string>
// struct: members are public by default
struct Point {
double x; // public
double y; // public
};
// class: members are private by default
class BankAccount {
std::string owner; // private
double balance; // private
public:
BankAccount(const std::string& name, double initial)
: owner{name}, balance{initial} {}
double get_balance() const { return balance; }
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
};
int main() {
Point p{3.0, 4.0}; // aggregate initialization
std::cout << p.x << ", " << p.y << '\n';
BankAccount acct{"Alice", 1000.0};
acct.deposit(500.0);
std::cout << acct.get_balance() << '\n'; // 1500
// acct.balance = -999; // ERROR: private member
return 0;
}Spécificateurs d'accès et le pointeur this
Le C++ fournit trois niveaux d'accès : public (tout le monde peut accéder), private (seulement la classe et ses amies), et protected (la classe, ses amies et les classes dérivées). Le pointeur this est un pointeur implicite vers l'objet courant, disponible dans chaque fonction membre non statique.
#include <iostream>
#include <string>
class Employee {
private:
std::string name_;
int id_;
protected:
double salary_; // accessible to derived classes
public:
Employee(const std::string& name, int id, double salary)
: name_{name}, id_{id}, salary_{salary} {}
// const member function: promises not to modify the object
const std::string& get_name() const { return name_; }
int get_id() const { return id_; }
// 'this' pointer usage — return *this for method chaining
Employee& set_name(const std::string& name) {
this->name_ = name; // 'this->' disambiguates
return *this;
}
Employee& set_id(int id) {
this->id_ = id;
return *this;
}
void print() const {
std::cout << "Employee{" << name_ << ", #" << id_
<< ", $" << salary_ << "}\n";
}
};
int main() {
Employee e{"Bob", 42, 75000.0};
e.set_name("Robert").set_id(43); // method chaining via *this
e.print(); // Employee{Robert, #43, $75000}
return 0;
}Membres statiques et friend
Les membres statiques appartiennent à la classe, pas à une instance particulière. Utilisez-les pour un état partagé ou des fonctions utilitaires. Le mot-clé friend accorde à une fonction non membre ou à une autre classe l'accès aux membres privés — utilisez-le avec parcimonie, car il brise l'encapsulation.
#include <iostream>
class Widget {
int value_;
// C++17: inline static — define in header, no .cpp needed
inline static int count_ = 0;
public:
explicit Widget(int v) : value_{v} { ++count_; }
~Widget() { --count_; }
static int get_count() { return count_; }
// friend function can access private members
friend std::ostream& operator<<(std::ostream& os, const Widget& w);
};
std::ostream& operator<<(std::ostream& os, const Widget& w) {
return os << "Widget(" << w.value_ << ')'; // accesses private value_
}
int main() {
Widget a{10}, b{20};
std::cout << Widget::get_count() << '\n'; // 2
std::cout << a << '\n'; // Widget(10)
{
Widget c{30};
std::cout << Widget::get_count() << '\n'; // 3
}
std::cout << Widget::get_count() << '\n'; // 2
return 0;
}Initialisation d'agrégat et initialiseurs désignés (C++20)
Un agrégat est une classe ou struct sans constructeurs déclarés par l'utilisateur, sans données membres non statiques privées/protégées, sans fonctions virtuelles et sans classes de base virtuelles. C++20 ajoute les initialiseurs désignés, vous permettant de nommer les membres que vous initialisez.
#include <iostream>
#include <string>
struct Config {
std::string host = "localhost";
int port = 8080;
bool use_tls = false;
int max_connections = 100;
};
int main() {
// Traditional aggregate initialization
Config c1{"example.com", 443, true, 200};
// C++20 designated initializers — much more readable
Config c2{
.host = "api.example.com",
.port = 443,
.use_tls = true
// .max_connections uses default value 100
};
std::cout << c2.host << ':' << c2.port << '\n';
std::cout << "TLS: " << std::boolalpha << c2.use_tls << '\n';
std::cout << "Max: " << c2.max_connections << '\n'; // 100
return 0;
}Rendez les données membres privées par défaut et n'exposez que ce qui est nécessaire via une interface publique minimale. Si une classe n'a pas d'invariant à maintenir (c'est juste un regroupement de données), utilisez un struct avec des membres publics à la place. N'ajoutez pas de getters et setters pour chaque champ — cela va à l'encontre de l'objectif de l'encapsulation. À la place, fournissez des opérations significatives comme deposit() et withdraw() plutôt que set_balance().
Si une fonction membre ne modifie pas l'état de l'objet, marquez-la const. Sans const, vous ne pouvez pas appeler la fonction sur un objet const ou via une référence const — ce qui arrive tout le temps quand on passe des objets par const&. Un const manquant sur un accesseur est une source courante d'erreurs de compilation dans les grandes bases de code.
structetclassne diffèrent que par l'accès par défaut :structest public,classest private- L'encapsulation protège les invariants de classe — utilisez des données
privateavec une interface publique significative - Marquez les fonctions membres non modifiantes
constpour qu'elles fonctionnent avec les référencesconst - Utilisez
inline static(C++17) pour les membres statiques définis dans les en-têtes - Les initialiseurs désignés C++20 rendent l'initialisation d'agrégat lisible et auto-documentée
frienddevrait être utilisé avec parcimonie — typiquement pour les surcharges d'opérateurs qui ont besoin d'un accès privé
Quiz — Testez vos connaissances
(15 XP)1. Quelle est la SEULE différence entre `struct` et `class` en C++ ?
2. Pourquoi devriez-vous marquer les fonctions membres accesseurs comme `const` ?
3. Que réalise `inline static int count_ = 0;` en C++17 ?