Menu
Coddy logo textTech

בנאי העברה

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

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

בנאי ההעברה מקבל הפניה לערך r, המסומנת באמצעות &&:

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    // בנאי העברה
    Buffer(Buffer&& other) noexcept {
        data = other.data;       // קח בעלות על המצביע
        size = other.size;
        other.data = nullptr;    // השאר את המקור במצב תקין
        other.size = 0;
    }
    
    ~Buffer() { delete[] data; }
};

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

בנאים להעברה נקראים בעת אתחול מאובייקטים זמניים או בעת שימוש ב־std::move():

Buffer createBuffer() {
    return Buffer(1000);    // נעשה שימוש בבנאי העברה (ערך מוחזר)
}

Buffer b1(1000);
Buffer b2(std::move(b1));   // העברה מפורשת - b1 ריק כעת

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

challenge icon

אתגר

קל

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

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

  • DataPacket.h: הגדירו מחלקה בשם DataPacket שמנהלת מטען של נתונים שלמים. למחלקה שלכם צריכים להיות:
    • שדות פרטיים: מצביע למערך של מספרים שלמים (payload), שדה size למספר האיברים, ושדה packetId (מחרוזת) לזיהוי המנה
    • בנאי עם פרמטרים שמקבל מזהה מנה וגודל, מקצה את המערך וממלא אותו בערכים מ־0 עד גודל-1. הדפיסו "Packet <id> created with size <size>"
    • בנאי העברה המסומן noexcept שמעביר את הבעלות על המטען מאובייקט המקור. הדפיסו "Packet <id> moved". זכרו להשאיר את המקור במצב ריק ותקין (nullptr, גודל 0)
    • הורס שמשחרר את הזיכרון אם המצביע אינו null, ומדפיס "Packet <id> destroyed"
    • מתודת getSize() שמחזירה את הגודל הנוכחי
    • מתודת getId() שמחזירה את מזהה המנה
    • מתודת getSum() שמחזירה את סכום כל האיברים במטען (מחזירה 0 אם המטען הוא null)
  • main.cpp: הדגימו בנייה באמצעות העברה על ידי קריאת מזהה מנה וגודל מהקלט. לאחר מכן:
    • צרו DataPacket בשם original עם ערכי הקלט
    • הדפיסו "Original - ID: <id>, Size: <size>, Sum: <sum>"
    • צרו מנה חדשה בשם transferred על ידי העברה מ־original באמצעות std::move()
    • הדפיסו "After move:"
    • הדפיסו "Original - ID: <id>, Size: <size>, Sum: <sum>"
    • הדפיסו "Transferred - ID: <id>, Size: <size>, Sum: <sum>"

פורמט הקלט יהיה:

  • שורה ראשונה: מזהה מנה (מחרוזת)
  • שורה שנייה: גודל (מספר שלם)

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

כללו את קובץ הכותרת שלכם ב־main.cpp באמצעות #include "DataPacket.h" ואל תשכחו לכלול את <utility> עבור std::move().

נסו בעצמכם

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

using namespace std;

int main() {
    // קרא את הקלט
    string packetId;
    int size;
    cin >> packetId;
    cin >> size;

    // TODO: צור DataPacket בשם 'original' עם ערכי הקלט

    // TODO: הדפס "Original - ID: <id>, Size: <size>, Sum: <sum>"

    // TODO: צור חבילה חדשה בשם 'transferred' באמצעות העברה מ-original
    // רמז: השתמש ב-std::move()

    // TODO: הדפס "After move:"

    // TODO: הדפס "Original - ID: <id>, Size: <size>, Sum: <sum>"

    // TODO: הדפס "Transferred - ID: <id>, Size: <size>, Sum: <sum>"

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

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

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

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