Surcharge d'opérateurs
Apprenez à surcharger correctement les opérateurs — membre vs non-membre, formes canoniques, opérateurs de flux, l'opérateur spaceship C++20 et les pièges courants.
Philosophie de la surcharge d'opérateurs
Le C++ vous permet de définir comment les opérateurs (+, ==, <<, [], etc.) fonctionnent pour vos propres types. La règle d'or : faites comme les ints. Les opérateurs surchargés devraient se comporter comme les utilisateurs l'attendent des types intégrés. Si a + b modifie a, vous avez violé les attentes des utilisateurs.
La surcharge d'opérateurs est ce qui rend les types comme std::string, std::vector et std::complex naturels à utiliser. Mais mal utilisée, elle crée du code confus et inmaintenable. Ne surchargez jamais les opérateurs pour faire quelque chose de surprenant.
Opérateurs membres vs non-membres
Certains opérateurs doivent être membres (=, [], ->, ()). D'autres sont mieux en tant que non-membres (en particulier les opérateurs binaires comme + et ==) pour permettre des conversions symétriques. Le patron canonique : implémentez += comme membre, puis + comme non-membre utilisant +=.
#include <iostream>
class Vec2 {
double x_, y_;
public:
Vec2(double x = 0, double y = 0) : x_{x}, y_{y} {}
// += is a member: modifies *this
Vec2& operator+=(const Vec2& rhs) {
x_ += rhs.x_;
y_ += rhs.y_;
return *this;
}
// Unary minus (member)
Vec2 operator-() const {
return Vec2{-x_, -y_};
}
// Accessors for non-member operators
double x() const { return x_; }
double y() const { return y_; }
// [] operator (must be member)
double& operator[](int i) {
return i == 0 ? x_ : y_;
}
const double& operator[](int i) const {
return i == 0 ? x_ : y_;
}
};
// + is a non-member using += (canonical form)
Vec2 operator+(Vec2 lhs, const Vec2& rhs) { // lhs by value!
lhs += rhs;
return lhs;
}
// == as non-member (symmetric: allows conversions on both sides)
bool operator==(const Vec2& a, const Vec2& b) {
return a.x() == b.x() && a.y() == b.y();
}
// Stream output operator (must be non-member)
std::ostream& operator<<(std::ostream& os, const Vec2& v) {
return os << '(' << v.x() << ", " << v.y() << ')';
}
int main() {
Vec2 a{1.0, 2.0}, b{3.0, 4.0};
Vec2 c = a + b; // uses non-member operator+
std::cout << c << '\n'; // (4, 6)
std::cout << -c << '\n'; // (-4, -6)
std::cout << c[0] << '\n'; // 4
std::cout << std::boolalpha << (a == b) << '\n'; // false
return 0;
}L'opérateur spaceship C++20 (<=>)
C++20 introduit l'opérateur de comparaison trilatérale <=> (l'« opérateur spaceship »). Une seule définition de operator<=> génère automatiquement les six opérateurs de comparaison (<, >, <=, >=, ==, !=). Utilisez = default quand la comparaison membre par membre est ce que vous voulez.
#include <iostream>
#include <compare>
#include <string>
class Version {
int major_;
int minor_;
int patch_;
public:
Version(int major, int minor, int patch)
: major_{major}, minor_{minor}, patch_{patch} {}
// Defaulted <=>: compares members in declaration order
auto operator<=>(const Version&) const = default;
friend std::ostream& operator<<(std::ostream& os, const Version& v) {
return os << v.major_ << '.' << v.minor_ << '.' << v.patch_;
}
};
class Student {
std::string name_;
double gpa_;
public:
Student(const std::string& name, double gpa)
: name_{name}, gpa_{gpa} {}
// Custom <=>: compare only by GPA
auto operator<=>(const Student& other) const {
return gpa_ <=> other.gpa_;
}
// Need separate == if <=> is not defaulted
bool operator==(const Student& other) const {
return gpa_ == other.gpa_;
}
friend std::ostream& operator<<(std::ostream& os, const Student& s) {
return os << s.name_ << " (GPA: " << s.gpa_ << ')';
}
};
int main() {
Version v1{2, 0, 0}, v2{1, 9, 5};
std::cout << v1 << " > " << v2 << "? "
<< std::boolalpha << (v1 > v2) << '\n'; // true
std::cout << (v1 == v2) << '\n'; // false
Student alice{"Alice", 3.9};
Student bob{"Bob", 3.7};
std::cout << (alice > bob) << '\n'; // true (higher GPA)
return 0;
}operator() — Objets fonctions
Surcharger operator() crée un foncteur (objet fonction). Les foncteurs peuvent contenir un état, les rendant plus flexibles que les pointeurs de fonctions simples. Ils sont utilisés intensivement avec les algorithmes STL.
#include <iostream>
#include <vector>
#include <algorithm>
// A functor that counts how many times it's called
class Counter {
int count_ = 0;
int threshold_;
public:
explicit Counter(int threshold) : threshold_{threshold} {}
bool operator()(int value) {
++count_;
return value > threshold_;
}
int calls() const { return count_; }
};
int main() {
std::vector<int> nums{1, 5, 3, 8, 2, 9, 4, 7, 6};
Counter above_five{5};
auto count = std::count_if(nums.begin(), nums.end(), above_five);
std::cout << count << " elements above 5\n"; // 4
// Functors can also be used directly
Counter is_positive{0};
std::cout << std::boolalpha;
std::cout << is_positive(42) << '\n'; // true
std::cout << is_positive(-1) << '\n'; // false
std::cout << "Calls: " << is_positive.calls() << '\n'; // 2
return 0;
}Ne surchargez pas les opérateurs pour faire des choses surprenantes. operator+ devrait additionner, pas soustraire. operator* sur une Matrix devrait faire la multiplication matricielle, pas la multiplication élément par élément (sauf si c'est ce que votre domaine attend). Certains opérateurs ne peuvent pas être surchargés : ::, ., .*, ?: et sizeof. De plus, surcharger && et || perd l'évaluation en court-circuit — presque jamais une bonne idée.
Suivez les formes canoniques : implémentez l'assignation composée (+=, -=) comme membres, puis définissez les opérateurs binaires (+, -) comme non-membres utilisant la version composée. Implémentez toujours operator== si vous implémentez operator< (ou mieux, utilisez <=> de C++20 pour obtenir les six). Implémentez les opérateurs de flux (<<, >>) comme des amies non-membres. Utilisez explicit sur les opérateurs de conversion pour empêcher les conversions implicites accidentelles.
- Suivez le principe « faites comme les ints » — les opérateurs devraient se comporter comme les utilisateurs l'attendent
- Implémentez l'assignation composée (
+=) comme membre, puis+comme non-membre utilisant+= - L'
operator<=>de C++20 génère les six opérateurs de comparaison à partir d'une seule définition - Surcharger
operator()crée des foncteurs — des objets qui peuvent être appelés comme des fonctions &&,||perdent l'évaluation en court-circuit quand ils sont surchargés — évitez de les surcharger- Certains opérateurs ne peuvent pas être surchargés :
::,.,.*,?:,sizeof
Quiz — Testez vos connaissances
(15 XP)1. Pourquoi `operator+` est-il typiquement implémenté comme une fonction non-membre ?
2. Que fait `auto operator<=>(const T&) const = default;` en C++20 ?
3. Pourquoi devriez-vous éviter de surcharger `operator&&` et `operator||` ?