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

قاعدة الثلاثة / الخمسة / الصفر

جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 25 من 104.

عندما يدير class لديك موارد مثل الذاكرة الديناميكية، تكون قد تعلمت أنك تحتاج إلى destructor مخصص، وcopy constructor، وmove constructor. لكن هناك مبدأً إرشاديًا يساعدك على تحديد أي functions خاصة بالأعضاء ينبغي تنفيذها: قاعدة الثلاثة والخمسة والصفر.

تنص Rule of Three على ما يلي: إذا قمت بتعريف أيٍّ من هذه الثلاثة، فينبغي أن تعرّف الثلاثة جميعًا:

  • Destructor
  • Copy constructor
  • Copy assignment operator

توسّع قاعدة العناصر الخمسة هذا في C++ الحديثة، بإضافة عمليات النقل:

  • Destructor
  • Copy constructor
  • Copy assignment operator
  • Move constructor
  • Move assignment operator
class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    ~Buffer() { delete[] data; }                          // 1. المُدمِّر
    Buffer(const Buffer& other);                          // 2. مُنشئ النسخ
    Buffer& operator=(const Buffer& other);               // 3. إسناد النسخ
    Buffer(Buffer&& other) noexcept;                      // 4. مُنشئ النقل
    Buffer& operator=(Buffer&& other) noexcept;           // 5. إسناد النقل
};

تُعد Rule of Zero النهج الأبسط: إذا لم يكن صفّك يدير الموارد مباشرةً، فلا تعرّف أيًا من هذه الدوال. دع المصرّف ينشئها، أو استخدم المؤشرات الذكية والحاويات القياسية التي تتولى إدارة الموارد نيابةً عنك.

class Player {
    std::string name;              // std::string يدير ذاكرته الخاصة
    std::vector<int> scores;       // std::vector يتعامل مع موارده
public:
    Player(std::string n) : name(n) {}
    // لا حاجة لدوال التدمير أو النسخ أو النقل!
};

يمنع اتباع هذه القواعد حدوث أخطاء مثل الحذف المزدوج، وتسربات الذاكرة، والمؤشرات المتدلية التي تحدث عند تعريف بعض العمليات مع فقدان عمليات أخرى.

challenge icon

التحدي

سهل

لنبنِ فئة TextBuffer تتبع Rule of Five: من خلال تنفيذ جميع special member functions الخمس لإدارة بيانات الأحرف المخصّصة ديناميكيًا بطريقة صحيحة. سيوضح هذا كيف تعمل عمليات copy وmove معًا لإنشاء فئة قوية تدير الموارد.

ستنشئ ملفين لتنظيم code الخاص بك:

  • TextBuffer.h: Define فئة TextBuffer تخزّن النص في مصفوفة أحرف مخصّصة ديناميكيًا. تحتاج فئتك إلى:
    • أعضاء Private: مؤشر char* called data لمحتوى النص، وsize_t length لطول السلسلة النصية (من دون null terminator)
    • Parameterized constructor يأخذ C-string (const char*)، ويخصّص الذاكرة، وينسخ content، ويطبع "TextBuffer created: <text>"
    • destructor يحرّر الذاكرة (إذا لم تكن null) ويطبع "TextBuffer destroyed"
    • copy constructor ينفّذ deep copy ويطبع "TextBuffer copied"
    • copy assignment operator يعالج self-assignment، وينظّف data الموجودة، وينفّذ deep copy، ويطبع "TextBuffer copy-assigned". أعد *this
    • move constructor (الموسوم بـ noexcept) ينقل الملكية ويطبع "TextBuffer moved". اترك المصدر في حالة فارغة صالحة
    • move assignment operator (الموسوم بـ noexcept) يعالج self-assignment، وينظّف data الموجودة، وينقل الملكية، ويطبع "TextBuffer move-assigned". أعد *this
    • دالة getText() تُرجع النص المخزّن (أعد سلسلة نصية فارغة "" إذا كانت data تساوي null)
    • دالة getLength() تُرجع length
  • main.cpp: اعرض جميع special member functions الخمس أثناء العمل. اقرأ سلسلة نصية من input، ثم:
    • Create كائن TextBuffer called original باستخدام نص input
    • Create copied باستخدام copy constructor من original
    • Create another باستخدام النص "Temporary"
    • استخدم copy assignment: another = original
    • Create moved بواسطة move-constructing من original باستخدام std::move()
    • Create target باستخدام النص "Target"
    • استخدم move assignment: target = std::move(copied)
    • اطبع "--- Final State ---"
    • اطبع "original: <text> (length: <len>)" لكل buffer: original وcopied وmoved وanother وtarget

بعد عمليات move، يجب أن تعرض كائنات المصدر (original وcopied) نصًا فارغًا مع length يساوي 0، بينما تحتفظ كائنات الوجهة بالبيانات المنقولة. يوضّح هذا Rule of Five أثناء العمل: تعمل functions الخمس معًا لضمان إدارة آمنة للموارد.

ضمّن <cstring> من أجل string functions مثل strlen وstrcpy، و<utility> من أجل std::move().

جرّب بنفسك

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

using namespace std;

int main() {
    string input;
    getline(cin, input);

    // TODO: أنشئ TextBuffer يُسمى 'original' بالنص المُدخل

    // TODO: أنشئ 'copied' باستخدام مُنشئ النسخ من 'original'

    // TODO: أنشئ 'another' بالنص "Temporary"

    // TODO: استخدم إسناد النسخ: another = original

    // TODO: أنشئ 'moved' بالبناء بالنقل من 'original' باستخدام std::move()

    // TODO: أنشئ 'target' بالنص "Target"

    // TODO: استخدم إسناد النقل: target = std::move(copied)

    // TODO: اطبع "--- Final State ---"

    // TODO: اطبع حالة كل buffer بهذا التنسيق:
    // "original: <text> (length: <len>)"
    // اطبع لـ: original, copied, moved, another, target

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

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم C++ عبر الإنترنت