העמסת אופרטור השמה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-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(); נעשות יעילות הרבה יותר.
אתגר
קלבואו נבנה מחלקה בשם DynamicString שמנהלת מערך תווים שהוקצה באופן דינמי ומטפלת כראוי בהשמה בין אובייקטים קיימים. זהו תרחיש מעשי שבו צריך להעביר בבטחה נתוני מחרוזת מאובייקט אחד לאחר, בלי דליפות זיכרון או מצביעים תלויים.
תיצרו שני קבצים כדי לארגן את הקוד:
DynamicString.h: הגדירו מחלקה בשםDynamicStringששומרת מחרוזת בסגנון C באמצעות זיכרון דינמי. המחלקה צריכה לנהל:- מצביע
char*פרטי לנתוני המחרוזת - ערך
size_tפרטי לאורך - בנאי שמקבל
const char*ויוצר עותק עמוק - מפרק שמשחרר כראוי את הזיכרון שהוקצה
- אופרטור השמת העתקה שמבצע העתקה עמוקה ומגן מפני השמה עצמית
- אופרטור השמת העברה שמעביר את הבעלות ביעילות
- מתודה בשם
c_str()שמחזירה את המחרוזת הפנימית (const) - מתודה בשם
length()שמחזירה את אורך המחרוזת (const)
אופרטור השמת ההעתקה שלכם חייב לבצע את ארבעת השלבים החיוניים: לבדוק אם מדובר בהשמה עצמית, לנקות משאבים קיימים, להעתיק את הנתונים החדשים ולהחזיר את
*this. אופרטור השמת ההעברה צריך לגנוב את המצביע של מקור ההעברה ולהשאיר את המקור במצב ריק ותקין (nullptrעם אורך 0).- מצביע
main.cpp: קראו שלוש מחרוזות מהקלט (כל אחת בשורה נפרדת). צרו שלושה אובייקטים מסוגDynamicString: אתstr1עם הקלט הראשון, אתstr2עם הקלט השני ואתstr3עם הקלט השלישי.הדגימו את אופרטורי ההשמה שלכם:
- הדפיסו את המצב ההתחלתי של שלוש המחרוזות
- השתמשו בהשמת העתקה:
str1 = str2; - הדפיסו את str1 ואת str2 לאחר השמת ההעתקה (לשניהם אמור להיות אותו תוכן)
- השתמשו בהשמת העברה:
str2 = std::move(str3); - הדפיסו את 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;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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++ אונליין