Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

تحميل عامل التعيين

جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 44 من 104.

يتم استدعاء معامل تعيين النسخ (=) عندما تقوم بتعيين كائن موجود إلى كائن آخر. وعلى عكس منشئ النسخ (الذي ينشئ كائناً جديداً)، فإن معامل التعيين يعمل مع كائن موجود بالفعل وقد يحتوي على موارد تحتاج إلى التنظيف أولاً.

class Buffer {
    int* data;
    size_t size;
    
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    ~Buffer() { delete[] data; }
    
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {              // 1. التحقق من التعيين الذاتي
            delete[] data;                  // 2. تنظيف المورد الموجود
            size = other.size;              // 3. نسخ البيانات
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;                       // 4. إرجاع *this
    }
};

يعد التحقق من التعيين الذاتي (this != &other) أمراً بالغ الأهمية. فبدونه، سيقوم b = b; بحذف البيانات قبل محاولة نسخها، مما يؤدي إلى سلوك غير محدد.

يقوم العامل بإرجاع *this بالمرجع لتمكين التسلسل مثل a = b = c;. وهذا يطابق سلوك الأنواع المضمنة.

بالنسبة للأصناف التي تدير الموارد، غالبًا ما ستقوم بتنفيذ كل من عاملي تعيين النسخ (copy) والنقل (move):

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;           // تنظيف المورد الحالي
        data = other.data;       // الاستيلاء على المورد
        size = other.size;
        other.data = nullptr;    // ترك المصدر في حالة صالحة
        other.size = 0;
    }
    return *this;
}

ينقل عامل تعيين النقل الملكية بدلاً من النسخ، مما يجعل عمليات مثل vec[0] = createBuffer(); أكثر كفاءة بكثير.

challenge icon

التحدي

سهل

لنقم ببناء فئة DynamicString تدير مصفوفة أحرف مخصصة ديناميكيًا وتتعامل بشكل صحيح مع عملية التعيين (assignment) بين الكائنات الموجودة. هذا سيناريو عملي حيث تحتاج إلى نقل بيانات السلسلة النصية بأمان من كائن إلى آخر دون حدوث تسرب للذاكرة (memory leaks) أو مؤشرات معلقة (dangling pointers).

ستقوم بإنشاء ملفين لتنظيم الكود الخاص بك:

  • DynamicString.h: قم بتعريف فئة DynamicString التي تخزن سلسلة نصية بنمط C باستخدام الذاكرة الديناميكية. يجب أن تدير فئتك ما يلي:
    • مؤشر char* خاص لبيانات السلسلة النصية
    • متغير size_t خاص للطول
    • منشئ (constructor) يأخذ const char* ويقوم بإنشاء نسخة عميقة (deep copy)
    • مدمر (destructor) يقوم بتحرير الذاكرة المخصصة بشكل صحيح
    • عامل تعيين النسخ (copy assignment operator) يقوم بإجراء نسخة عميقة مع الحماية من التعيين الذاتي (self-assignment protection)
    • عامل تعيين النقل (move assignment operator) يقوم بنقل الملكية بكفاءة
    • دالة c_str() تعيد السلسلة النصية الداخلية (const)
    • دالة length() تعيد طول السلسلة النصية (const)

    يجب أن يتبع عامل تعيين النسخ الخطوات الأربع الأساسية: التحقق من التعيين الذاتي، تنظيف الموارد الموجودة، نسخ البيانات الجديدة، وإرجاع *this. يجب أن يقوم عامل تعيين النقل "بسرقة" مؤشر المصدر وترك المصدر في حالة فارغة صالحة (nullptr مع طول 0).

  • main.cpp: اقرأ ثلاث سلاسل نصية من الإدخال (كل واحدة في سطر منفصل). قم بإنشاء ثلاثة كائنات DynamicString: str1 مع الإدخال الأول، str2 مع الإدخال الثاني، و str3 مع الإدخال الثالث.

    قم باستعراض عوامل التعيين الخاصة بك:

    1. اطبع الحالة الأولية لجميع السلاسل الثلاث
    2. استخدم تعيين النسخ: str1 = str2;
    3. اطبع str1 و str2 بعد تعيين النسخ (يجب أن يكون لكليهما نفس المحتوى)
    4. استخدم تعيين النقل: str2 = std::move(str3);
    5. اطبع str2 بعد تعيين النقل، وطول str3 (يجب أن يكون 0 بعد نقله)

    تنسيق المخرجات:

    Initial:
    str1: <value>
    str2: <value>
    str3: <value>
    After copy (str1 = str2):
    str1: <value>
    str2: <value>
    After move (str2 = std::move(str3)):
    str2: <value>
    str3 length: 0

استخدم <cstring> من أجل strlen و strcpy. قم بتمييز عامل تعيين النقل بـ noexcept. قم بتضمين <utility> في main.cpp من أجل std::move. لا تنسَ حراس الرأس (header guards) في ملف الرأس الخاص بك.

ورقة مرجعية

يقوم عامل تعيين النسخ (copy assignment operator) (=) بتعيين كائن موجود إلى كائن آخر. يجب أن يتعامل مع تنظيف الموارد الموجودة قبل نسخ البيانات الجديدة.

هيكل عامل تعيين النسخ

ClassName& operator=(const ClassName& other) {
    if (this != &other) {              // 1. Self-assignment check
        // 2. Clean up existing resources
        // 3. Copy data from other
    }
    return *this;                       // 4. Return *this
}

مثال مع الذاكرة الديناميكية

class Buffer {
    int* data;
    size_t size;
    
public:
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {
            delete[] data;                  // Clean up existing resource
            size = other.size;
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;
    }
};

يمنع التحقق من التعيين الذاتي (this != &other) حذف البيانات قبل نسخها في حالات مثل b = b;.

إرجاع *this بالمرجع (by reference) يتيح التسلسل: a = b = c;.

عامل تعيين النقل (Move Assignment Operator)

يقوم عامل تعيين النقل بنقل الملكية بدلاً من النسخ، مما يحسن الكفاءة:

ClassName& operator=(ClassName&& other) noexcept {
    if (this != &other) {
        // Clean up current resource
        // Steal the resource from other
        // Leave other in valid state (nullptr, 0, etc.)
    }
    return *this;
}

مثال على تعيين النقل

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;
        data = other.data;       // Steal the resource
        size = other.size;
        other.data = nullptr;    // Leave source valid
        other.size = 0;
    }
    return *this;
}

قم بتمييز تعيين النقل بـ noexcept للحصول على أداء مثالي مع الحاويات القياسية (standard containers).

جرّب بنفسك

#include <iostream>
#include <string>
#include <utility>
#include "DynamicString.h"

using namespace std;

int main() {
    // قراءة ثلاث سلاسل نصية من المدخلات
    string input1, input2, input3;
    getline(cin, input1);
    getline(cin, input2);
    getline(cin, input3);

    // إنشاء ثلاثة كائنات DynamicString
    DynamicString str1(input1.c_str());
    DynamicString str2(input2.c_str());
    DynamicString str3(input3.c_str());

    // TODO: طباعة الحالة الأولية لجميع السلاسل الثلاث
    // التنسيق:
    // Initial:
    // str1: <value>
    // str2: <value>
    // str3: <value>

    // TODO: استخدام تخصيص النسخ: str1 = str2;

    // TODO: طباعة str1 و str2 بعد تخصيص النسخ
    // التنسيق:
    // After copy (str1 = str2):
    // str1: <value>
    // str2: <value>

    // TODO: استخدام تخصيص النقل: str2 = std::move(str3);

    // TODO: طباعة str2 بعد النقل، وطول str3
    // التنسيق:
    // After move (str2 = std::move(str3)):
    // str2: <value>
    // str3 length: 0

    return 0;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming