Menu
Coddy logo textTech

העמסת אופרטור השמה

חלק מהיחידה תכנות מונחה עצמים במסלול ה-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;. כך פועלים גם טיפוסים מובנים.

במחלקות שמנהלות משאבים, לעיתים קרובות תממשו גם אופרטורים להשמה בהעתקה וגם אופרטורים להשמה בהעברה:

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 שמנהלת מערך תווים שהוקצה באופן דינמי ומטפלת כראוי בהשמה בין אובייקטים קיימים. זהו תרחיש מעשי שבו צריך להעביר בבטחה נתוני מחרוזת מאובייקט אחד לאחר, בלי דליפות זיכרון או מצביעים תלויים.

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

  • DynamicString.h: הגדירו מחלקה בשם DynamicString ששומרת מחרוזת בסגנון C באמצעות זיכרון דינמי. המחלקה צריכה לנהל:
    • מצביע char* פרטי לנתוני המחרוזת
    • ערך size_t פרטי לאורך
    • בנאי שמקבל const char* ויוצר עותק עמוק
    • מפרק שמשחרר כראוי את הזיכרון שהוקצה
    • אופרטור השמת העתקה שמבצע העתקה עמוקה ומגן מפני השמה עצמית
    • אופרטור השמת העברה שמעביר את הבעלות ביעילות
    • מתודה בשם 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. אל תשכחו להוסיף שומרי כותרת לקובץ הכותרת שלכם.

נסו בעצמכם

#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בחנו את עצמכם

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

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

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