בנאי העתקה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 20 מתוך 104.
בנאי העתקה יוצר אובייקט חדש כהעתק של אובייקט קיים. הוא נקרא כשמאתחלים אובייקט מאובייקט אחר מאותו סוג, מעבירים אובייקט לפי ערך או מחזירים אובייקט לפי ערך.
בנאי ההעתקה מקבל הפניה קבועה לאובייקט מאותה מחלקה. זו חייבת להיות הפניה: העברת other לפי ערך תדרוש בעצמה העתקה, שתפעיל שוב ושוב את בנאי ההעתקה לנצח. ההפניה היא const כי יצירת עותק לעולם לא אמורה לשנות את המקור:
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, אחת בכל שורה
- צור אובייקט
פורמט הקלט יהיה:
- שורה ראשונה: קיבולת (מספר שלם)
- שורה שנייה: ההודעה הראשונה (מחרוזת)
- שורה שלישית: ההודעה השנייה (מחרוזת)
אם ההעתקה העמוקה שלך ממומשת כראוי, בעותק הגיבוי אמורות להיות 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++ אונליין