בנאי העתקה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 20 מתוך 104.
בנאי העתקה יוצר אובייקט חדש כהעתק של אובייקט קיים. הוא נקרא כשמאתחלים אובייקט מאובייקט אחר מאותו סוג, מעבירים אובייקט לפי ערך או מחזירים אובייקט לפי ערך.
בנאי ההעתקה מקבל הפניה קבועה לאובייקט מאותה מחלקה:
class Player {
std::string name;
int health;
public:
Player(std::string n, int h) : name(n), health(h) {}
// בנאי העתקה
Player(const Player& other) {
name = other.name;
health = other.health;
}
};
Player original("Hero", 100);
Player copy = original; // בנאי ההעתקה נקרא
Player another(original); // קורא גם לבנאי ההעתקהאם לא תגדירו בנאי העתקה, המהדר ייצור אחד שמבצע העתקה שטחית — הוא מעתיק ישירות את הערך של כל איבר. זה עובד היטב עבור טיפוסים פשוטים, אך גורם לבעיות כשהמחלקה שלכם מנהלת זיכרון דינמי.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
// בנאי העתקה עמוקה
Buffer(const Buffer& other) : size(other.size) {
data = new int[size]; // הקצה זיכרון חדש
for (size_t i = 0; i < size; i++)
data[i] = other.data[i]; // העתק את התוכן
}
~Buffer() { delete[] data; }
};ללא בנאי העתקה מותאם אישית, שני האובייקטים היו מצביעים לאותו אזור בזיכרון. כשאחד מהם מושמד, לאחר היה מצביע תלוי. העתקה עמוקה מקצה זיכרון חדש ומעתיקה את הנתונים בפועל, וכך מבטיחה שכל אובייקט מחזיק במשאבים משלו.
אתגר
קלבואו נבנה מערכת היסטוריית הודעות שמדגימה את החשיבות של העתקה עמוקה כאשר המחלקה שלך מנהלת זיכרון דינמי. תיצרו מחלקה בשם MessageLog ששומרת הודעות במערך שהוקצה דינמית, ותממשו בנאי העתקה מתאים כדי להבטיח שלכל עותק יהיה זיכרון עצמאי משלו.
תיצרו שני קבצים כדי לארגן את הקוד שלכם:
MessageLog.h: הגדירו מחלקה בשםMessageLogשמנהלת אוסף הודעות באמצעות זיכרון דינמי. למחלקה שלכם צריכים להיות:- חברים פרטיים: מצביע למערך מחרוזות לאחסון הודעות,
capacityלמספר ההודעות המרבי, ו-countלמספר ההודעות הנוכחי - בנאי עם פרמטרים שמקבל קיבולת, מקצה את המערך בערימה ומאתחל את count ל-0
- בנאי העתקה שמבצע העתקה עמוקה: מקצה זיכרון חדש ומעתיק את כל ההודעות מהאובייקט המקור
- הורס שמשחרר את הזיכרון שהוקצה ומדפיס
"MessageLog destroyed" - מתודה
addMessage(string msg)שמוסיפה הודעה אם יש מקום - מתודה
getCount()שמחזירה את מספר ההודעות הנוכחי - מתודה
getMessage(int index)שמחזירה את ההודעה באינדקס הנתון
- חברים פרטיים: מצביע למערך מחרוזות לאחסון הודעות,
main.cpp: הדגימו שבנאי ההעתקה שלכם יוצר עותק עצמאי. קראו ערך קיבולת ושתי הודעות מהקלט, ואז:- צרו
MessageLogבשםoriginalעם הקיבולת שהתקבלה בקלט - הוסיפו את שתי ההודעות ל-
original - צרו עותק בשם
backupבאמצעות בנאי ההעתקה - הוסיפו את ההודעה השלישית
"New message"ל-originalבלבד - הדפיסו
"Original count: <count>" - הדפיסו
"Backup count: <count>" - הדפיסו את כל ההודעות מתוך
backup, הודעה אחת בכל שורה
- צרו
פורמט הקלט יהיה:
- שורה ראשונה: קיבולת (מספר שלם)
- שורה שנייה: ההודעה הראשונה (מחרוזת)
- שורה שלישית: ההודעה השנייה (מחרוזת)
אם ההעתקה העמוקה שלכם מומשה כראוי, העותק backup יכיל 2 הודעות, בעוד שהמקור יכיל 3: כך תוכיחו שיש להם זיכרון עצמאי. ללא בנאי העתקה מתאים, שניהם היו חולקים את אותו מערך, ושינויים באחד מהם היו משפיעים על השני!
כללו את קובץ הכותרת שלכם ב-main.cpp באמצעות #include "MessageLog.h".
נסו בעצמכם
#include <iostream>
#include <string>
#include "MessageLog.h"
using namespace std;
int main() {
// קרא את הקלט
int capacity;
cin >> capacity;
cin.ignore();
string msg1, msg2;
getline(cin, msg1);
getline(cin, msg2);
// TODO: צור MessageLog בשם 'original' עם הקיבולת מהקלט
// TODO: הוסף את שתי ההודעות ל-original
// TODO: צור עותק בשם 'backup' באמצעות בנאי ההעתקה
// TODO: הוסף את "New message" רק ל-original
// TODO: הדפס "Original count: <count>"
// TODO: הדפס "Backup count: <count>"
// TODO: הדפס את כל ההודעות מ-backup, אחת בכל שורה
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++ אונליין