Quá tải toán tử
Tìm hiểu cách nạp chồng các toán tử một cách chính xác — thành viên và không phải thành viên, biểu mẫu chính tắc, toán tử luồng, toán tử tàu vũ trụ C++20 và các cạm bẫy phổ biến.
Triết lý nạp chồng toán tử
C++ cho phép bạn xác định cách hoạt động của các toán tử (+,==,<<,[], v.v.) cho các loại của riêng bạn. Quy tắc vàng: làm những gì mà số nguyên làm. Các toán tử quá tải sẽ hoạt động như người dùng mong đợi từ các kiểu có sẵn. Nếua + bsửa đổiathì bạn đã vi phạm mong đợi của người dùng.
Quá tải toán tử là nguyên nhân khiến các loại nhưstd::string,std::vectorvàstd::complextrở nên tự nhiên. Nhưng sử dụng không tốt sẽ tạo ra mã khó hiểu, không thể bảo trì được. Đừng bao giờ làm quá tải người vận hành để làm điều gì đó đáng ngạc nhiên.
Thành viên và nhà điều hành không phải thành viên
Một số nhà khai thác phải là thành viên (=,[],->,()). Những người khác sẽ tốt hơn nếu không phải là thành viên (đặc biệt là các toán tử nhị phân như+và==) để cho phép chuyển đổi đối xứng. Mẫu chuẩn: triển khai+=với tư cách thành viên, sau đó+với tư cách không phải thành viên bằng+=.
#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;
}Người điều hành tàu vũ trụ C++20 (<=>)
C++20 giới thiệu toán tử so sánh ba chiều<=>("toán tử tàu vũ trụ"). Một định nghĩaoperator<=>duy nhất tự động tạo tất cả sáu toán tử so sánh (<,>,<=,>=,==,!=). Sử dụng= defaultkhi so sánh giữa các thành viên là điều bạn muốn.
#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() — Đối tượng hàm
Quá tảioperator()sẽ tạo ra functor (đối tượng hàm). Functor có thể giữ trạng thái, làm cho chúng linh hoạt hơn các con trỏ hàm đơn giản. Chúng được sử dụng rộng rãi với các thuật toán 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;
}Đừng quá tải người vận hành để làm những việc đáng ngạc nhiên.operator+nên cộng chứ không phải trừ.operator*trênMatrixphải thực hiện phép nhân ma trận chứ không phải phép nhân theo phần tử (trừ khi đó là điều mà miền của bạn mong đợi). Một số toán tử không thể bị quá tải:::,.,.*,?:vàsizeof. Ngoài ra, việc quá tải&&và||sẽ làm mất khả năng đánh giá ngắn mạch - hầu như không bao giờ là một ý tưởng hay.
Thực hiện theo các biểu mẫu chuẩn: triển khai phép gán kết hợp (+=,-=) với tư cách là thành viên, sau đó xác định toán tử nhị phân (+,-) là không phải thành viên bằng cách sử dụng phiên bản kết hợp. Luôn triển khaioperator==nếu bạn triển khaioperator<(hoặc tốt hơn, hãy sử dụng C++20<=>để có được cả sáu). Triển khai các toán tử luồng (<<,>>) với tư cách là bạn bè không phải thành viên. Sử dụngexplicittrên toán tử chuyển đổi để ngăn các chuyển đổi tiềm ẩn do vô tình.
- Tuân theo nguyên tắc "làm những gì int làm" — các toán tử phải hành xử như người dùng mong đợi
- Triển khai phép gán kết hợp (
+=) với tư cách thành viên, sau đó+với tư cách không phải thành viên bằng+= operator<=>của C++20 tạo ra tất cả sáu toán tử so sánh từ một định nghĩa duy nhất- Quá tải
operator()tạo ra các functor - các đối tượng có thể được gọi giống như các hàm &&,||mất khả năng đánh giá ngắn mạch khi quá tải — tránh làm chúng bị quá tải- Một số toán tử không thể bị quá tải:
::,.,.*,?:,sizeof
Câu đố — Kiểm tra kiến thức
(15 XP)1. Tại sao`operator+`thường được triển khai dưới dạng hàm không phải thành viên?
2. `auto operator<=>(const T&) const = default;`làm gì trong C++20?
3. Tại sao bạn nên tránh quá tải`operator&&`và`operator||`?