Menu
Coddy logo textTech

כלל השלושה / החמישה / האפס

חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 25 מתוך 104.

כשמחלקה מנהלת משאבים כמו זיכרון דינמי, למדת שעליך להגדיר בנאי הרס מותאם אישית, בנאי העתקה ובנאי העברה. אבל יש עיקרון מנחה שעוזר לך להחליט אילו פונקציות מיוחדות של איברים לממש: כלל השלושה, החמישה והאפס.

כלל השלושה קובע: אם מגדירים אחד משלושת אלה, יש להגדיר את שלושתם:

  • הורס
  • בנאי העתקה
  • אופרטור השמת העתקה

כלל החמישה מרחיב זאת עבור C++ מודרנית, ומוסיף פעולות העברה:

  • הורס
  • בנאי העתקה
  • אופרטור השמה בהעתקה
  • בנאי העברה
  • אופרטור השמה בהעברה
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. השמת העברה
};

כלל האפס הוא הגישה הפשוטה ביותר: אם המחלקה שלך אינה מנהלת משאבים ישירות, אל תגדיר אף אחת מהפונקציות האלה. תן למהדר ליצור אותן, או השתמש במצביעים חכמים ובמכולות סטנדרטיות שמנהלות עבורך את המשאבים.

class Player {
    std::string name;              // std::string מנהלת את הזיכרון שלה בעצמה
    std::vector<int> scores;       // std::vector מנהלת את המשאבים שלה
public:
    Player(std::string n) : name(n) {}
    // אין צורך בפונקציות הרס, העתקה או העברה!
};

הקפדה על הכללים האלה מונעת באגים כמו מחיקה כפולה, דליפות זיכרון ומצביעים תלויים, שמתרחשים כאשר פעולות מסוימות מוגדרות אך אחרות חסרות.

challenge icon

אתגר

קל

בואו נבנה מחלקה TextBuffer שפועלת לפי כלל החמישה: נממש את כל חמש פונקציות החבר המיוחדות כדי לנהל כראוי נתוני תווים שהוקצו באופן דינמי. כך נדגים כיצד פעולות העתקה והעברה פועלות יחד ליצירת מחלקה חזקה שמנהלת משאבים.

ניצור שני קבצים כדי לארגן את הקוד:

  • TextBuffer.h: הגדירו מחלקה TextBuffer ששומרת טקסט במערך תווים שהוקצה באופן דינמי. המחלקה צריכה לכלול:
    • חברים פרטיים: מצביע char* בשם data עבור תוכן הטקסט, ו-size_t length עבור אורך המחרוזת (לא כולל תו הסיום null)
    • בנאי עם פרמטרים שמקבל מחרוזת C (const char*), מקצה זיכרון, מעתיק את התוכן ומדפיס "TextBuffer created: <text>"
    • הורס שמשחרר זיכרון (אם הוא אינו null) ומדפיס "TextBuffer destroyed"
    • בנאי העתקה שמבצע העתקה עמוקה ומדפיס "TextBuffer copied"
    • אופרטור השמת העתקה שמטפל בהשמה לעצמו, מנקה את הנתונים הקיימים, מבצע העתקה עמוקה ומדפיס "TextBuffer copy-assigned". החזירו *this
    • בנאי העברה (מסומן ב-noexcept) שמעביר בעלות ומדפיס "TextBuffer moved". השאירו את המקור במצב ריק ותקין
    • אופרטור השמת העברה (מסומן ב-noexcept) שמטפל בהשמה לעצמו, מנקה את הנתונים הקיימים, מעביר בעלות ומדפיס "TextBuffer move-assigned". החזירו *this
    • מתודה getText() שמחזירה את הטקסט השמור (החזירו מחרוזת ריקה "" אם data הוא null)
    • מתודה getLength() שמחזירה את האורך
  • main.cpp: הדגימו בפעולה את כל חמש פונקציות החבר המיוחדות. קראו מחרוזת טקסט מהקלט, ואז:
    • צרו TextBuffer בשם original עם טקסט הקלט
    • צרו את copied באמצעות בנאי ההעתקה מתוך original
    • צרו את another עם הטקסט "Temporary"
    • השתמשו בהשמת העתקה: another = original
    • צרו את moved באמצעות בנאי העברה מתוך original בעזרת std::move()
    • צרו את target עם הטקסט "Target"
    • השתמשו בהשמת העברה: target = std::move(copied)
    • הדפיסו "--- Final State ---"
    • הדפיסו "original: <text> (length: <len>)" עבור כל מאגר: original, copied, moved, another, target

לאחר פעולות ההעברה, אובייקטי המקור (original ו-copied) אמורים להציג טקסט ריק עם אורך 0, בעוד שאובייקטי היעד מכילים את הנתונים שהועברו. כך מודגם כלל החמישה בפעולה: כל חמש הפונקציות פועלות יחד כדי להבטיח ניהול משאבים בטוח.

כללו את <cstring> עבור פונקציות מחרוזת כמו 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: הדפס את המצב של כל מאגר בפורמט הזה:
    // "original: <text> (length: <len>)"
    // הדפס עבור: original, copied, moved, another, target

    return 0;
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר C++ אונליין