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;. وهذا يطابق سلوك الأنواع المدمجة.

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

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

ينقل عامل تعيين النقل (move assignment operator) الملكية بدلاً من النسخ، مما يجعل عمليات مثل 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) في ملف الرأس الخاص بك.

جرّب بنفسك

#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: بعد إسناد النسخ str2 و str1 طباعة
    // التنسيق:
    // After copy (str1 = str2):
    // str1: <value>
    // str2: <value>

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

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

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

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

جميع دروس Object Oriented Programming