הפוסט הזה דן בנושא הקשור ל-constructor. מומלץ למי שלא מכיר את נושא ה-constructors לקרוא את הפוסט הקודם בנושא.
ה-constructor יבצע בד"כ אתחול של ה-class data members.
ה-constructor יבצע בד"כ אתחול של ה-class data members.
נראה תחילה אתחול פרמטרים בלי Initialization List. בדוגמא שלפנינו הוא יכניס ערכים לca, ל-cb ולמשתנה נוסף שהוא אובייקט מסוג Example (אתחול הערכים מודגש בצהוב):
- /*
- * rect.h
- *
- * Created on: Mar 12, 2012
- * Author: ronen
- */
- #ifndef RECT_H_
- #define RECT_H_
- class Rect{
- private:
- int ca;
- int cb;
- Example cexample; // assume Example is a known class type
- int csum;
- public:
- Rect(int a, int b, Example example){ca= a; cb = b; cexample = example; csum = a+b;}
- .
- .
- .
- };
- #endif /* RECT_H_ */
שורה 18: ה-constructor מאתחל את ca ו- cb עם הערכים a ו-b בהתאמה.
דרך יותר יעילה לביצוע האיתחול היא בעזרת initialization list - סדרת אתחול.
הנה המימוש של constructor הנ"ל בפורמט של סדרת אתחול:
Rect(int a, int b): ca(a), cb(b), cexample(example) {csum = a+b;}
מה מתבצע בשורת האתחול:
פשוט מאד. מופעל constructor עבור כל אחד מהפרמטרים. בדוגמא הנ"ל מופעלים שלושה constructors:
שני constructors של int שמקבלים את הפרמטרים a ו-b.
constructor של example המקבל את הערך example.
ומה היה מתבצע אם היינו משמיטים חלק או את כל הפרמטרים משורת האתחול?
פשוט מאד. ה-constructor עבור כל אחד מהמשתנים היה מופעל בכל מקרה. זה לא כתוב בקוד, אבל זה נעשה בכל אופן (implicitly). איזה constructor היה מופעל? ה-default constructor. כן, לכל type, גם אם הוא סטנדרטי יש constructor.
על סמך מה שנאמר לע"יל, השימוש ב-initialization list יעיל יותר. הנה הסיבה:
עבור data שמאותחלת ע"י ה-constructor בלי initialization list, מתבצעות שתי פעולות:
1. הפעלת ה-default constructor. את זה אנו לא רואים בקוד. (כמו שאמרנו, זה נעשה implicitly).
2. הכנסת הערך ב-constructor של Rect שכרוכה שוב בהפעלת ה-constructor של כל אחד מהמשתנים (הפקודות המודגשות בצהוב):
Rect(int a, int b, Example example){ca= a; cb = b; cexample = example; csum = a+b;}
עבור data שנמצאת ב-initialization list, נעשית רק פעולת ה-constructor אחת.
ההבדל אינו משמעותי עבור regular types כמו int או string, אבל יכול להיות משמעותי עבור אובייקטים גדולים.
מדוע להשתמש ב-initialization list?
אני יכול לחשוב על 3 סיבות עיקריות:
1. יעילות - כפי שהוסבר כאן למעלה.
2. אתחול constants ואתחול reference parameters: לא ניתן לאתחל בתוך ה-constructors. ראה דוגמא למטה.
3. יכולת הבחנה בשמות - ראה הסבר + דוגמא בהמשך.
אז נמשיך עם דוגמא המראה כיצד ניתן לאתחל constants ו-reference parameters עם initialization list ואח"כ נראה את היכולת להפרדת שמות הנתמכת ב-initialization list.
דוגמא לאתחול constants ו-reference parameters:
- /*
- * initList.cpp
- *
- * Created on: Mar 17, 2012
- * Author: ronen halevy
- *
- */
- #include <iostream>
- using namespace std;
- class Rect{
- private:
- const int CONST_VAL;
- int& reference;
- int a;
- int b;
- public:
- Rect(int a, int b, int c);
- void set(int a, int b);
- void show();
- };
- Rect::Rect(int edge_a, int edge_b, int c): reference(a), CONST_VAL(c){
- a=edge_a;
- b=edge_b;
- // referance = a; // can't be initialized here
- // CONST_VAL = c; / can't be initialized here
- }
- void Rect::set(int edge_a, int edge_b){
- a = edge_a;
- b = edge_b;
- cout << "from set method: a= " << a<<" b= " << b << endl;
- }
- void Rect::show(){
- cout << "edge a = " << a << endl << "edge b = " << b << endl << "\CONST_VAL = " << CONST_VAL << endl << "reference = "<<referenc
בשורות 13 ו-14 מוגדרים constant ו-reference בהתאמה. (מה זה constant ו-reference? בקיצור: constant הוא ערך שלא ניתן לשינוי בתוך התוכנית, ו-reference הוא למעשה מן "משתנה צללים" שמצביע על משתנה אחר. זה שונה מה-pointer המוכר מ-c. מקווה שזה מובן. ארחיב על reference בפוסט אחר.)
הנה נסיון לאתחל משתנה const ומשתנה reference בשתי דרכים:
שורה 25: השמת הערכים לשני הנ"ל בתוך ה-initialization list. זו הדרך הנכונה
שורה 25: השמת הערכים לשני הנ"ל בתוך ה-initialization list. זו הדרך הנכונה
שורה 28 ו-29: ניסיתי לאתחל את הערכים הנ"ל בתוך ה-constructor. קיבלתי שגיאה!
והנה הפלט שהודפס למסך ע"י המתודה show:
edge a = 100
edge b = 200
CONST_VAL = 1
reference = 100
ועתה נסביר את ההבחנה בשמות.
נסתכל על ה-constructor משורה 25 למעלה. העתקתי אותו הנה:
- Rect::Rect(int edge_a, int edge_b, int c): reference(a), CONST_VAL(c){
- a=edge_a;
- b=edge_b;
- // referance = a; // can't be initialized here
- // CONST_VAL = c; / can't be initialized here
- }
בשורות 2 ו-3 מאתחלים את a ו-b שהם class members, כששמות הפרמטים של ה-constructor הם edge_a ו-edge_b. זה עובד מצוין. אבל מה היה קורה אם שמות הפרמטרים היו זהים לשמות ה-class members כלומר ל-a ול-b? זה לא היה עובד!
אני מתכוון לזה:
- Rect::Rect(int a, int b, int c): reference(a), CONST_VAL(c){
- a=a;
- b=b;
- // referance = a; // can't be initialized here
- // CONST_VAL = c; / can't be initialized here
- }
ערכי האתחול לא היו מגיעים ל-a ו-b! המערכת לא מסוגלת להבחין בין הפרמטר לבין ה-class member כשיש להם שמות זהים.
בעזרת initialization list המערכת כן תבחין בשמות.
הנה ה-constructor וה-initialization list שכולל אתחול של a ו-b:
Rect::Rect(int a, int b, int c): a(a), b(b), reference(a), CONST_VAL(c){
//a=edge_a;
//b=edge_b;
// referance = a; // can't be initialized here
// CONST_VAL = c; / can't be initialized here
}
ה-constructor הנ"ל עובד מצוין!
עוד מספר הערות לביום:
1. סדר האתחול נעשה על פי סדר ההכרזה של המשתנים , ולא לפי הסדר של ה-initialization list.
2. כשנדבר על inheritance ואתחול ה-base constructors, נראה שגם שם האתחול נעשה באופן דומה ל-initialization list.
אין תגובות:
הוסף רשומת תגובה