ऑपरेटर ओवरलोडिंग
ऑपरेटर को सही तरीके से ओवरलोड करना सीखें — मेंबर बनाम नॉन-मेंबर, कैनोनिकल रूप, स्ट्रीम ऑपरेटर, C++20 का स्पेसशिप ऑपरेटर और आम गलतियाँ।
ऑपरेटर ओवरलोडिंग का सिद्धांत
C++ आपको यह परिभाषित करने देता है कि ऑपरेटर (+, ==, <<, [] आदि) आपके अपने टाइप के लिए कैसे काम करें। सुनहरा नियम: वही करें जो int करते हैं। ओवरलोड किए गए ऑपरेटर को वैसा ही व्यवहार करना चाहिए जैसा उपयोगकर्ता बिल्ट-इन टाइप से अपेक्षा करते हैं। अगर 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 थ्री-वे कंपैरिज़न ऑपरेटर (three-way comparison operator) <=> (“स्पेसशिप ऑपरेटर”) लाता है। डिफ़ॉल्ट किया गया एक 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) (फ़ंक्शन ऑब्जेक्ट) बनता है। फ़ंक्टर अवस्था (state) रख सकते हैं, जिससे वे सादे फ़ंक्शन पॉइंटर से अधिक लचीले होते हैं। 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+ को जोड़ना चाहिए, घटाना नहीं। Matrix पर operator* को मैट्रिक्स गुणन करना चाहिए, तत्व-दर-तत्व गुणन नहीं (जब तक आपके डोमेन में वही अपेक्षित न हो)। कुछ ऑपरेटर ओवरलोड नहीं किए जा सकते: ::, ., .*, ?: और sizeof। साथ ही, && और || को ओवरलोड करने पर शॉर्ट-सर्किट मूल्यांकन खो जाता है — यह लगभग कभी अच्छा विचार नहीं है।
कैनोनिकल रूपों का पालन करें: कंपाउंड असाइनमेंट (+=, -=) को मेंबर के रूप में लागू करें, फिर बाइनरी ऑपरेटर (+, -) को कंपाउंड वर्ज़न की मदद से नॉन-मेंबर के रूप में परिभाषित करें। अगर आप operator< लागू करते हैं, तो operator== भी हमेशा लागू करें (या बेहतर, C++20 के <=> को डिफ़ॉल्ट करके छहों पाएँ)। स्ट्रीम ऑपरेटर (<<, >>) को नॉन-मेंबर फ़्रेंड के रूप में लागू करें। आकस्मिक इम्प्लिसिट कन्वर्ज़न रोकने के लिए कन्वर्ज़न ऑपरेटर पर explicit इस्तेमाल करें।
- “वही करें जो int करते हैं” सिद्धांत का पालन करें — ऑपरेटर को वैसा व्यवहार करना चाहिए जैसा उपयोगकर्ता अपेक्षा करते हैं
- कंपाउंड असाइनमेंट (
+=) को मेंबर के रूप में लागू करें, फिर+को नॉन-मेंबर के रूप में+=की मदद से - C++20 का डिफ़ॉल्ट किया हुआ
operator<=>एक ही परिभाषा से छहों तुलना ऑपरेटर जनरेट करता है (खुद लिखे गए ऑपरेटर के साथ बराबरी वाले ऑपरेटर अलग से चाहिए) operator()को ओवरलोड करने से फ़ंक्टर बनते हैं — ऐसे ऑब्जेक्ट जिन्हें फ़ंक्शन की तरह कॉल किया जा सकता है&&,||ओवरलोड करने पर शॉर्ट-सर्किट मूल्यांकन खो देते हैं — इन्हें ओवरलोड करने से बचें- कुछ ऑपरेटर ओवरलोड नहीं किए जा सकते:
::,.,.*,?:,sizeof
क्विज़ — अपना ज्ञान परखें
(15 XP)1. `operator+` को आमतौर पर नॉन-मेंबर फ़ंक्शन के रूप में क्यों लागू किया जाता है?
2. C++20 में `auto operator<=>(const T&) const = default;` क्या करता है?
3. `operator&&` और `operator||` को ओवरलोड करने से क्यों बचना चाहिए?