انتقل إلى المحتوى

Operator Overloading

Learn how to overload operators correctly — member vs non-member, canonical forms, stream operators, the C++20 spaceship operator, and common pitfalls.

Operator Overloading Philosophy

C++ lets you define how operators (+,==,<<,[], etc.) work for your own types. The golden rule: do what the ints do. Overloaded operators should behave as users expect from built-in types. Ifa + bmodifiesa, you have violated user expectations.

Operator overloading is what makes types likestd::string,std::vector, andstd::complexfeel natural. But used poorly, it creates confusing, unmaintainable code. Never overload operators to do something surprising.

Member vs Non-Member Operators

Some operators must be members (=,[],->,()). Others are better as non-members (especially binary operators like+and==) to allow symmetric conversions. The canonical pattern: implement+=as a member, then+as a non-member using+=.

operators.cpp
#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 Spaceship Operator (<=>)

C++20 introduces the three-way comparison operator<=>(the "spaceship operator"). A singleoperator<=>definition auto-generates all six comparison operators (<,>,<=,>=,==,!=). Use= defaultwhen member-wise comparison is what you want.

spaceship.cpp
#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()إلى إنشاء عامل (كائن دالة). يمكن للوظائف الاحتفاظ بالحالة، مما يجعلها أكثر مرونة من مؤشرات الوظائف العادية. يتم استخدامها على نطاق واسع مع خوارزميات STL.

functor.cpp
#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 نقطة خبرة)

1. لماذا يتم تنفيذ`operator+`عادةً كوظيفة غير عضو؟

2. ماذا يفعل`auto operator<=>(const T&) const = default;`في C++20؟

3. لماذا يجب عليك تجنب التحميل الزائد على`operator&&`و`operator||`؟