בנאי העברה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-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.
אתגר
קלבואו נבנה מערכת של מנות נתונים שמדגימה כיצד בנאים להעברה מעבירים בעלות על משאבים ביעילות. תיצרו מחלקה בשם 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;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP
קבצים חיצונייםבנייה וקומפילציה של C++קובצי Header וקובצי מקורמרחבי שמות והיקףמבוא ל-OOP ב-C++מחלקות לעומת אובייקטיםמצביע thisמתודות (פונקציות איבר)תכונות (חברי נתונים)יסודות הבנאים וההורסיםסיכום – מחשבון פשוט4מאפייני מחלקה
חברי מופע לעומת חברים סטטייםגטרים וסטריםפונקציות איבר קבועותמילת המפתח mutableמתודות ומשתנים סטטייםפונקציות ומחלקות ידידותסיכום – מנהל חשבונות בנק7ירושה
ירושה בסיסיתרמות גישה בירושהסדר הקריאות לבנאי ולמפרקדריסת מתודותפונקציות וירטואליות ו־VTableירושה מרובהירושה וירטואליתסיכום – היררכיית עובדים2ניהול זיכרון
זיכרון Stack לעומת Heapמצביעים והפניותזיכרון דינמי (new/delete)מצביעים חכמים ב-C++RAII ב-C++סיכום – מנהל מערכים דינמיים5כימוס
מצייני גישה ב-C++מצייני גישה לעומקהסתרת מידעמבנה לעומת מחלקהמחלקות מקוננות ופנימיותסיכום – מערכת רשומות תלמידים8רב־צורתיות
רב־צורתיות בזמן קומפילציה לעומת בזמן ריצההעמסת פונקציותפונקציות וירטואליות — מבט נוסףפונקציות וירטואליות טהורותמחלקות מופשטותתכנון ממשקים ב־C++המרה דינמית ו־RTTIסיכום — מחשבון צורות3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין