יום שני

Friend Function, Friend Class, Friend Member Class

אז חשבתם ש-class member מסוג private או protected ניתנים לגישה אך ורק מתוך האובייקט? מסתבר שאפשר לגשת ל-private ו-protected גם מבחוץ. איך עושים זאת? פשוט. בעזרת הסימון: friend.
נראה שלושה סוגים של friends:
1. Friend Function - זו פונקציה שאינה שייכת ל-class ובכל זאת יכולה לגשת לאלמנטים החסויים שלו.
2. Friend Class - זה class שיורשה לגשת לאלמנטים החסויים של class אחר.
3. Friend Member Class - זוהי מתודה בתוך class שתורשה לגשת לחלקים החסויים של class אחר. 

האם ה-friendship עולה בקנה אחד אם עקרונות ה-information hiding של c++? הדעות בעניין חלוקות. יש כאלה שיעדיפו להמנע משימוש ב-friends, ובמקום זה לאחד classes או להכליל פונקציות בתוך classes. אחרים יטענו friendship מחזקת את יכולות ה-c++. בכל אופן, בואו ונכיר את הנושא מקרוב.
נראה את שלושת הסוגים בדוגמאות.
Friend Function.
נראה דוגמא עם פונקציה בשם set_code שאינה שייכת ל-class, ובכל זאת היא ניגשת ישירות ל- private data member שלו.
שם ה-class אליו היא ניגשת הוא Safe  וה-private data הוא משתנה בשם secret_code/ ראו שורה 22.
שורה 23: הגישה הישירה ל-secret_code מתוך set_code.
ההכרזה שאפשרה זאת, נמצאת בשורה 19:
    friend void set_code (Safe &safe, int new_code);
כאן מוכרזת הפונקציה, בתוך ה-class עם תוספת friend בראש. שימו לב: set_code אינה member ב-Safe.
ההוכחה לכך ש set_code אינה member נמצאת בשורה 22:  אם היא היתה member, שם ה-class היה מופיעה כקידומת לשם הפונקציה.
לסיכום: שורה 23 לא היתה אפשרית לולא הכרזת ה-friend בשורה 19.
נסתכל על main, הפונקציה המפעילה:
שורה 30: קריאה ל-set_code, היא אכן מכניסה ערך חדש ל-secret_code/
והערה נוספת לגבי שורות 19 ו-22: הסימון & Safe מציין שהאובייקט יועבר לפונקציה כ- reference value.
שתי סיבות להעברת האובייקט לא by value:
1. המתודה set_code משנה את הערך של האובייקט. העברה by_value לא היתה משנה את האובייקט אלא רק עותק שלו.
2. בנוסף לסיבה הראשונה, שפוסלת את האפשרות להעברה by value בכל מקרה, העברה by reference יעילה יותר, כי היא חוסכת את העתקה של האובייקט. במקום העתקה של האוביקט מועבר reference אליו. הנה קישור לפוסט בנושא Reference Variables.






  1. /*
  2.  * friend_func.cpp
  3.  *
  4.  *  Created on: Mar 20, 2012
  5.  *      Author: ronen halevy
  6.  */
  7. #include <iostream>
  8. using namespace std;

  9. class Safe{
  10. private:
  11. int tot_deposit;
  12. int secrete_code;
  13. public:
  14. void show(){cout << "secrete_code "<<secrete_code<< endl;}
  15. Safe():secrete_code(6666){}
  16.     friend void set_code (Safe &safe, int new_code);
  17. };

  18. void set_code (Safe &safe, int new_code){
  19. safe.secrete_code = new_code;
  20. }


  21. int main(){
  22. Safe safe;
  23. set_code(safe, 67777);
  24. safe.show();
  25. }


הנה הפלט, המוכיח כי הכתיבה הצליחה:

secrete_code 67777


זה היה Friend Function.
ועתה נראה דוגמא ל-Friend Class:

נציג שהי classes:
האחד - Safe - שמייצג כספת של בנק, ובתוכה מופקד כסף.
השני -  Bank - שניגש ישירות למספר אובייקטים מסוג Safe, קורא את הסכום שבכספת ומסכם אצלו.
ה-Bank מורשה לגשת ל-private של Safe היות שהוא מוגדר כחבר של Safe.
הנה התוכנית:


  1. /*
  2.  * friend_class.cpp
  3.  *
  4.  *  Created on: Mar 20, 2012
  5.  *      Author: ronen halevy
  6.  */

  7. #include <iostream>
  8. using namespace std;

  9. //class Safe;

  10. class Safe{
  11. private:
  12. int secrete_code;
  13. int tot_deposit;

  14. public:
  15. void show(){cout << "tot_deposit "<<tot_deposit<< endl;}
  16. Safe(int a):secrete_code(6666),tot_deposit(a) {}
  17.     friend class Bank;
  18. };

  19. class Bank{
  20. private:
  21. int sum_all_safes;
  22. public:
  23. Bank():sum_all_safes(0){}
  24. void acc_safe(Safe &safe);
  25. void show(){cout << "sum_all_safes "<<sum_all_safes<< endl;}
  26. };
  27. void Bank::acc_safe(Safe &safe){
  28. Bank::sum_all_safes +=safe.tot_deposit;
  29. }

  30. int main(){
  31. Safe safe1(999);
  32. Safe safe2(1230);
  33. Bank bank;
  34. bank.acc_safe(safe1);
  35. bank.acc_safe(safe2);
  36. bank.show();
  37. }


Safe ו-Bank צבועים בירוק וצהוב בהתאמה.
נסתכל על החלקים המעניינים ב-Safe:
שורה 22:
  friend class Bank;
זו השורה שמגדירה את Bank כ-friend של Safe.
והחלק המעניין ב- Bank:
שורה 34: כאן Bank ניגש ל-tot_deposit, שהוא private.
שימו לב לשורה 30 ו-33 ולסימון & Safe. האוביקט לא מועבר -by value אלא by reference. זה חוסך למערכת את ההעתקה של האובייקט ל-stack. הנה קישור לפוסט בנושא Reference Variables.
נסתכל על main, הפונקציה המפעילה:
בשורה 38 ו-39 נוצרים שני אובייקטים מסוג Safe. בכספות "מופקדים" סכומים של 999 ו-1230.
בשורה 41 ו-42 האובייקט מסוג Bank מסכם את הסכומים המופקדים בשתי הכספות.
שורה 43: מדפיס את הסכום שהצטבר. 
הנה הפלט:

sum_all_safes 2229

Friend Member Class
ראינו שני סוגים של friends: פונקציה ו-class. בדוגמא של ה-class, רק המתודה acc_safe  של ה-Bank ניגשה ל-private של Safe. לפיכך, אפשר היה להסתפק בכך ש-acc_safe בלבד תהיה friend של safe.

התוכנית דומה לזו שהוצגה בסעיף הקודם. נוספו מספר שינויים "קוסמטיים":




  1. /*
  2.  * friend_member_class.cpp
  3.  *
  4.  *  Created on: Mar 20, 2012
  5.  *      Author: ronen halevy
  6.  */

  7. #include <iostream>
  8. using namespace std;

  9. class Safe;

  10. class Bank{
  11. private:
  12. int sum_all_safes;
  13. public:
  14. Bank():sum_all_safes(0){}
  15. void acc_safe(Safe &safe);
  16. void show(){cout << "sum_all_safes "<<sum_all_safes<< endl;}
  17. };


  18. class Safe{
  19. private:
  20. int secrete_code;
  21. int tot_deposit;

  22. public:
  23. void show(){cout << "tot_deposit "<<tot_deposit<< endl;}
  24. Safe(int a):secrete_code(6666),tot_deposit(a) {}
  25.     friend void Bank::acc_safe(Safe & safe);
  26. };

  27. void Bank::acc_safe(Safe &safe){
  28. Bank::sum_all_safes +=safe.tot_deposit;
  29. }

  30. int main(){
  31. Safe safe1(999);
  32. Safe safe2(1230);
  33. Bank bank;
  34. bank.acc_safe(safe1);
  35. bank.acc_safe(safe2);
  36. bank.show();
  37. }





נתייחס לשינויים לעומת הסעיף הקודם:
שורה 32: זו השורה שמגדירה את המתודה acc_safe של Bank כ-friend של Safe. הנה היא:
   friend void Bank::acc_safe(Safe & safe);
כזכור, בדוגמא הקודמת ההגדרה היתה:

  friend class Bank;

שימו לב: שינינו את סדר הגדרת ה-classes: כעת ההגדרה של Bank מקדימה את Safe. אם הסדר היה הפוך - כמו בדוגמא הקודמת - היינו מקבלים שגיאת קומפילציה:
בשורה:
   friend void Bank::acc_safe(Safe & safe);
הודעת השגיאה היתה:
"Bank has not been declared"
השורה הנ"ל שנמצאת ב-Safe, אמורה להכיר את המתודה acc_safe של Bank, אבל Bank מוגדרת רק בהמשך.
זו הסיבה שהחלפנו את הסדר.


כעת, עם החלפת הסדר נוצרה בעיה אצל Bank : שורה 32 מתייחסת ל-Safe. אם היינו מקמפלים את זה, היתה  ומתקבלת שם הודעת שגיאה:
"Safe has not been declared"

גם את זה נפתור, ע"י הוספת שורה 12:
Class Safe.
זה נקראה Forward Declaration - הקפצנו את ההכרזה של Safe קדימה. כל הבעיות נפתרו!

4 תגובות:

  1. צריך לתקן את מיספור של שורות קוד בשתי דוגמאות האחרונות

    השבמחק
  2. לא בטוח שזיהיתי את הבעיה. עדין יש בעיה במיספור?

    השבמחק
  3. למה צריך את זה בכלל? סתם מסובך. מה היתרונות?

    השבמחק