Υπερφόρτωση χειριστή
Μάθετε πώς να υπερφορτώνετε σωστά τους χειριστές — μέλη έναντι μη μέλους, κανονικές φόρμες, χειριστές ροής, χειριστή διαστημόπλοιου C++20 και κοινές παγίδες.
Φιλοσοφία υπερφόρτωσης χειριστή
Το C++ σάς επιτρέπει να ορίσετε πώς λειτουργούν οι τελεστές (+,==,<<,[], κ.λπ.) για τους δικούς σας τύπους. Ο χρυσός κανόνας: κάντε ό,τι κάνουν τα ints. Οι υπερφορτωμένοι χειριστές θα πρέπει να συμπεριφέρονται όπως περιμένουν οι χρήστες από τους ενσωματωμένους τύπους. Εάν τοa + bτροποποιήσει τοa, έχετε παραβιάσει τις προσδοκίες των χρηστών.
Η υπερφόρτωση χειριστή είναι αυτό που κάνει τύπους όπως ταstd::string,std::vectorκαιstd::complexνα αισθάνονται φυσικά. Αλλά όταν χρησιμοποιείται ελάχιστα, δημιουργεί μπερδεμένο, μη συντηρήσιμο κώδικα. Ποτέ μην υπερφορτώνετε τους χειριστές για να κάνετε κάτι εκπληκτικό.
Μέλος έναντι μη μελών χειριστών
Ορισμένοι χειριστές πρέπει να είναι μέλη (=,[],->,()). Άλλοι είναι καλύτεροι ως μη μέλη (ειδικά δυαδικοί τελεστές όπως οι+και==) για να επιτρέπουν συμμετρικές μετατροπές. Το κανονικό μοτίβο: εφαρμόστε το+=ως μέλος και μετά το+ως μη μέλος χρησιμοποιώντας το+=.
#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;
}C++20 Διαστημόπλοιο Χειριστής (<=>)
Το C++20 παρουσιάζει τον τελεστή σύγκρισης τριών κατευθύνσεων<=>(ο "χειριστής διαστημόπλοιου"). Ένας μόνος ορισμόςoperator<=>δημιουργεί αυτόματα και τους έξι τελεστές σύγκρισης (<,>,<=,>=,==,!=). Χρησιμοποιήστε το= defaultόταν η σύγκριση μεταξύ μελών είναι αυτό που θέλετε.
#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() — Αντικείμενα συνάρτησης
Η υπερφόρτωση τουoperator()δημιουργεί έναν functor (αντικείμενο συνάρτησης). Οι συντελεστές μπορούν να κρατούν κατάσταση, καθιστώντας τους πιο ευέλικτους από τους απλούς δείκτες συνάρτησης. Χρησιμοποιούνται εκτενώς με τους αλγόριθμους 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;
}Μην υπερφορτώνετε τους χειριστές για να κάνουν εκπληκτικά πράγματα. Τοoperator+πρέπει να προσθέτει, όχι να αφαιρεί. Τοoperator*σε έναMatrixθα πρέπει να κάνει πολλαπλασιασμό μήτρας, όχι πολλαπλασιασμό βάσει στοιχείων (εκτός αν αυτό περιμένει ο τομέας σας). Ορισμένοι χειριστές δεν μπορούν να υπερφορτωθούν:::,.,.*,?:καιsizeof. Επίσης, η υπερφόρτωση των&&και||χάνει την αξιολόγηση βραχυκυκλώματος — σχεδόν ποτέ δεν είναι καλή ιδέα.
Ακολουθήστε τις κανονικές μορφές: εφαρμόστε την ανάθεση σύνθετων (+=,-=) ως μέλη και, στη συνέχεια, ορίστε τους δυαδικούς τελεστές (+,-) ως μη μέλη χρησιμοποιώντας την σύνθετη έκδοση. Εφαρμόζετε πάντα τοoperator==εάν εφαρμόζετε τοoperator<(ή καλύτερα, χρησιμοποιήστε το C++20<=>για να λάβετε και τα έξι). Εφαρμόστε τελεστές ροής (<<,>>) ως φίλους που δεν είναι μέλη. Χρησιμοποιήστε τοexplicitσε τελεστές μετατροπής για να αποτρέψετε τυχαίες σιωπηρές μετατροπές.
- Ακολουθήστε την αρχή "κάνε ό,τι κάνουν τα ints" — οι χειριστές πρέπει να συμπεριφέρονται όπως περιμένουν οι χρήστες
- Εφαρμόστε την ανάθεση σύνθεσης (
+=) ως μέλος και, στη συνέχεια, το+ως μη μέλος χρησιμοποιώντας το+= - Το
operator<=>του C++20 δημιουργεί και τους έξι τελεστές σύγκρισης από έναν μόνο ορισμό - Η υπερφόρτωση του
operator()δημιουργεί συντελεστές — αντικείμενα που μπορούν να ονομαστούν σαν συναρτήσεις - Τα
&&,||χάνουν την αξιολόγηση βραχυκυκλώματος όταν υπερφορτώνονται — αποφύγετε την υπερφόρτωσή τους - Δεν είναι δυνατή η υπερφόρτωση ορισμένων τελεστών:
::,.,.*,?:,sizeof
Κουίζ — Δοκιμάστε τις γνώσεις σας
(15 XP)1. Γιατί το`operator+`υλοποιείται συνήθως ως συνάρτηση που δεν είναι μέλος;
2. Τι κάνει το`auto operator<=>(const T&) const = default;`στο C++20;
3. Γιατί πρέπει να αποφύγετε την υπερφόρτωση των`operator&&`και`operator||`;