יום שבת

Constructors - Initialization Lists

הפוסט הזה דן בנושא הקשור ל-constructor. מומלץ למי שלא מכיר את נושא ה-constructors לקרוא את הפוסט הקודם בנושא.
ה-constructor יבצע בד"כ אתחול של ה-class data members.
נראה תחילה אתחול פרמטרים בלי Initialization List. בדוגמא שלפנינו הוא יכניס ערכים לca, ל-cb ולמשתנה נוסף שהוא אובייקט מסוג Example (אתחול הערכים מודגש בצהוב):

  1. /*
  2.  * rect.h
  3.  *
  4.  *  Created on: Mar 12, 2012
  5.  *      Author: ronen
  6.  */


  7. #ifndef RECT_H_
  8. #define RECT_H_
  9. class Rect{
  10. private:
  11. int ca;
  12. int cb;
  13. Example cexample; // assume Example is a known class type
  14. int csum;
  15. public:
  16. Rect(int a, int b, Example example){ca= a; cb = b; cexample = example; csum = a+b;}
  17. .
  18. .
  19. .
  1. };

  2. #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:


  1. /*
  2.  * initList.cpp
  3.  *
  4.  *  Created on: Mar 17, 2012
  5.  *      Author: ronen halevy
  6.  *
  7.  */

  8. #include <iostream>
  9. using namespace std;
  10. class Rect{
  11. private:
  12. const int CONST_VAL;
  13. int& reference;
  14. int a;
  15. int b;

  16. public:

  17. Rect(int a, int b, int c);
  18. void set(int a, int b);
  19. void show();
  20. };

  21. Rect::Rect(int edge_a, int edge_b, int c): reference(a), CONST_VAL(c){
  22. a=edge_a;
  23. b=edge_b;
  24. // referance = a; // can't be initialized here
  25. //  CONST_VAL = c; / can't be initialized here
  26. }

  27. void Rect::set(int edge_a, int edge_b){
  28. a = edge_a;
  29. b = edge_b;
  30. cout << "from set method: a= " << a<<"  b= " << b << endl;

  31. }
  32. void  Rect::show(){
  33. 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. זו הדרך הנכונה
    שורה 28 ו-29: ניסיתי לאתחל את הערכים הנ"ל בתוך ה-constructor. קיבלתי שגיאה!

    והנה הפלט שהודפס למסך ע"י המתודה show:



    edge a = 100
    edge b = 200
    CONST_VAL = 1
    reference = 100



    ועתה נסביר את ההבחנה בשמות.
    נסתכל על ה-constructor משורה 25 למעלה. העתקתי אותו הנה:


    1. Rect::Rect(int edge_a, int edge_b, int c): reference(a), CONST_VAL(c){
    2. a=edge_a;
    3. b=edge_b;
    4. // referance = a; // can't be initialized here
    5. //  CONST_VAL = c; / can't be initialized here
    6. }
    בשורות 2  ו-3 מאתחלים את a ו-b שהם class members, כששמות הפרמטים של ה-constructor הם edge_a ו-edge_b. זה עובד מצוין. אבל מה היה קורה אם שמות הפרמטרים היו זהים לשמות ה-class members כלומר ל-a ול-b? זה לא היה עובד!
    אני מתכוון לזה:

    1. Rect::Rect(int a, int b, int c): reference(a), CONST_VAL(c){
    2. a=a;
    3. b=b;
    4. // referance = a; // can't be initialized here
    5. //  CONST_VAL = c; / can't be initialized here
    6. }
    ערכי האתחול לא היו מגיעים ל-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.


    אין תגובות:

    הוסף רשומת תגובה