280 likes | 489 Views
Object Oriented Programming. Tirgul 12: Design Patterns Singleton Smart Pointer. Design Patterns Elements of Reusable Object-Oriented Software. ספרות. By: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides http://st-www.cs.illinois.edu/users/patterns/dpbook/dpbook.html.
E N D
Object Oriented Programming Tirgul 12: Design Patterns Singleton Smart Pointer
Design PatternsElements of Reusable Object-Oriented Software ספרות By: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides http://st-www.cs.illinois.edu/users/patterns/dpbook/dpbook.html
מה זה design patterns? • אוסף של בעיות (תכנותיות) פופולריות והפיתרון להם • איננו תלוי שפה • אם כי, ישנן שפות אשר יתקשו במימוש נקי באי-אלו תבניות, או ביתר קלות בתבניות אחרות • ישנם הרבה מאוד design pattern (כ-30 תבניות פופולריות)
משפחות של design patterns כמו בצבא, מתחלק ל-3: • Creational patterns • עוסק ב"איך ליצור אוביקט" מתאים • נלמד: Singleton Pattern • Structural patterns • עוסקות בהרכבה של אוביקטים • Behavioral patterns • תקשורת בין אוביקטים • למדנו:Iterator
PrinterSingleton Printer Client Printer Client Printer Client
למה צריך singleton? • כאשר אנו רוצים להבטיח כי ישנו רק מופע (instance) אחד (או, מקסימום N מופעים) של מחלקה • יש רק printer spooler אחד • ישנו file system אחד • ישנו רק windows manager אחד • לתת גישה גלובלית לאותה מחלקה • Printer Clients • נרצה ליצור לוגיקת singleton אצל האוביקט ה"מיוחד", כאשר המשתמשים אינם מודעים לכך
אולי משתנה גלובלי/סטטי ? • משתנה גלובלי / סטטי. אומנם יתן גישה גלובלית אך: • זיהום מרחב השמות (הרבה משתנים גלובלים) • לא ימנע יצירת מופעים רבים (אפשר עדיין לעשות new) • יתכן ואין לנו את כל הנתונים לצורך יצירת מחלקה • ניצור מחלקה גם אם לא נשתמש בה • C++ לא מבטיחה סדר יצירה. לכן, singleton-ים שונים, אשר תלויים אחד בשני, לא יוכלו להיבנות (אין לנו שליטה על סדר היצירה). • נרצה ליצור לוגיקת singleton אצל האוביקט, כאשר המשתמשים אינם מודעים לכך
אולי פונק' סטטיות ? • אם יש רק מופע אחד, למה לא לעשות את כל הפונק' במחלקה סטטיות? • אם בעתיד נרצה לשנות את כמות המופעים • שינוי קוד מסיבי, גם אצל המשתמשים • ב- C++ פונק' סטטיות הם אף פעם לא virtual • לא נוכל ליצור היררכיה (ירושה) של המחלקה
אולי משתנה סטטי במחלקה ? • משתנה סטטי של המחלקה ייווצר עם השימוש הראשון במחלקה • יתכן שהשימוש יהיה ע"י קריאה לפונק' סטטית כלשהיא • סדר היצירה של משתנים סטטים איננו מוגדר:יתכן ומשתנה סטטי ירצה להשתמש ב-singleton עוד לפני יצירתו • נרצה lazy initilization: איתחול רק בזמן השימוש ראשון • לפני זה אין צורך • יתכן כי רק אז, יהיה לנו מספיק נתונים בשביל היצירה
פתרון להסתיר את ה-:Constructor//PrinterSingleton.h class PrinterSingleton { public: static PrinterSingleton * GetInstance(); void Print(const char *); protected : PrinterSingleton (); // cannot invoke new from outside private: static PrinterSingleton * the_singleton; int m_iPagesPrinted; };
// PrinterSingleton.cpp PrinterSingleton * PrinterSingleton::the_singleton = NULL; PrinterSingleton * PrinterSingleton::GetInstance() { if( NULL == the_singleton ) { the_singleton = new PrinterSingleton (); } return the_singleton; } PrinterSingleton:: PrinterSingleton ( ) { m_iPagesPrinted =0; } void PrinterSingleton:: Print (const char* sPrint) { // ... perform printing code m_iPagesPrinted++; cout << "Printing page number “ << m_iPagesPrinted << endl; cout << sPrint; }
// main.cpp void main_func() { // … PrinterSingleton::GetInstance() ->Print("Print1 \n"); } int main() { main_func(); PrinterSingleton::GetInstance()->Print("Print2 \n"); return 0; } Output: Printing page number 1 Print1 Printing page number 2 Print2
ומה עושים כאשר ל-singleton יש Dtor? • איך נקרא ל-D’tor של singleton בסוף התוכנית? • מאחר ואין שום משתנה אשר מחזיק את האובייקט אף אחד לא יקרא ל-D’tor • פתרון: ליצור משתנה סטטי אשר מחזיק את האובייקט
public: //Inner Class class Keeper { public: Keeper(Logger* logger) : m_logger(logger) { } ~Keeper() { delete m_logger; } private: Logger* m_logger; }; friend class Logger::Keeper; }; // Logger.h class Logger { public: static Logger* GetInstance(); virtual bool write(string str); private: static Logger* the_singleton; fstream m_flog; protected: Logger(); // Ctor is protected virtual ~Logger();
Logger::~Logger() { m_flog.close(); } bool Logger::write(string str) { m_flog.write(str.c_str(), str.length()); m_flog.flush(); return true; } // Logger.cpp Logger* Logger::the_singleton = NULL; Logger* Logger::GetInstance() { if (!the_singleton) the_singleton = new Logger(); return the_singleton; } Logger::Logger() { static Keeper keeper(this); m_flog.open("ourlog.out", ios::app | ios::out ); }
// main.cpp int main() { Logger::GetInstance()->write("Log1\n"); // ... Logger::GetInstance()->write("Log2\n"); return 0; } Output (“ourlog.out”): Log1 Log2
singleton במערכות עם כמה תהליכים במצב של ריבוי תהליכים (mutithreading) נצטרך להגן על GetInstance( ) מפני כניסה מקבילה Singleton* Singleton::GetInstance( ) { // lock – code to avoid concurrent activation if (!the_singleton) the_singleton = new Singleton(); // unlock – code to release locking return the_singleton; }
Smart Pointer – למה? • אובייקט שנראה ופועל כמו מצביע רגיל, אך נותן פונקציונאליות נוספת. • מנסים להשיג שתי מטרות עיקריות: • תכנות בטוח יותר • תכנות קל יותר. • ממומשים כמחלקות template אשר • מכמיסות את המצביע המובנה בשפה • עוקפות אופרטורים סטנדרטיים של המצביע.
Smart Pointer – המשך ... • אחד השימושים הנפוצים במצביעים חכמים הוא לטובת Garbage Collection. • הרעיון: למנוע דליפות זיכרון • חשוב לזכור למחוק את המצביע לאובייקט. • קורה ששוכחים לקרוא במפורש ל- delete • ownership - במקרים מורכבים, המצביע מוחזר מפונקציה ולא ברור מי אחראי על שחרור המצביע • dangling pointer – השמה בטעות של המצביע במבנה נתונים, ואז, בטעות, מחיקת המצביע ה"מקורי"
Smart Pointer – המשך ... • קיימות שפות בהן מנגנון ה- Garbage Collection הוא מובנה בשפה (למשל Java) • נגדיר מצביע חכם שהוא מחלקה ש: • מכילה את המצביע לאובייקט עצמו • מונה התייחסויות לאותו אובייקט. • כל פעם שנוצר מצביע חכם חדש לאובייקט - מגדילים את המונה באחד. • כשהמצביע החכם כבר לא מצביע לאובייקט אנחנו נפחית את המונה באחת. • כאשר המונה מתאפס, האובייקט נהרס.
// SPtr.h: interface for the SPtr class. template <class T> class RefCnt { private: T* m_pObj; int m_cnt; public: RefCnt(T* pObj) : m_pObj(pObj), m_cnt(0) {} ~RefCnt() { assert(m_cnt==0); delete m_pObj; } T* operator*() {return m_pObj;} int inc() {m_cnt++; cout<<"increment to:"<<m_cnt<<endl; return m_cnt;} int dec() {m_cnt--; cout<<"decrement to:"<<m_cnt<<endl;return m_cnt;} };
void operator=(const SPtr<T>& sp) { if(this!=&sp) { //first we need to delete the current // object, so we just decrement its // reference count and check if it's zero. if(m_ptr && (m_ptr->dec()==0)) delete m_ptr; m_ptr = sp.m_ptr; m_ptr->inc(); } } //we want to wrap the pointer to class T, // so we need to supply the -> operator T* operator->() { return (*m_ptr); } }; template <class T> class SPtr { private: RefCnt<T>* m_ptr; // hide new/delete operators to disallow // allocating a SPtr<T> from the heap. void* operator new(size_t) {} void operator delete(void*) {} public: SPtr() : m_ptr(NULL) { /* default Ctor */} SPtr(T* p) { m_ptr = new RefCnt<T>(p); m_ptr->inc(); } SPtr(const SPtr<T>& sp) { m_ptr = sp.m_ptr; m_ptr->inc(); } ~SPtr() { if(m_ptr->dec()==0) delete m_ptr; }
class String { // String.h: implementation of string private: char* m_str; public: String(char* str) { m_str = new char[strlen(str)+1]; strcpy(m_str, str); } String(const String& str) { m_str = new char[strlen(str.m_str)+1]; strcpy(m_str, str.m_str); } ~String() { delete [] m_str; m_str=NULL; } }; typedef SPtr<String> SPtrString;
String* GetTokenBad(istream& in) { char buffer[100]; in>>buffer; String* token = new String(buffer); return token; //who is suppose to delete this one??? }
//converted to sptr: calls SPtr<String>(String*) with token. SPtrString GetTokenGood(istream& in) { char buffer[100]; in>>buffer; String* token = new String(buffer); return token; } void main() { // int i; // SPtr<int>* p = new SPtr<int>(&i); // ERROR: 'new' : cannot access private member declared in class 'SPtr<int>' SPtrString spStr; cout<<"Please enter a word: "; spStr = GetTokenGood(cin); } // call default Ctor Output: increment to:1 increment to:2 decrement to:1 decrement to:0 // call operator = // remove temporal object // end main: remove local variable: spStr
אז מה עםgarbage collection ? • מדוע לא משתמשים ב-smart pointers כמימוש אוטומטי שלgarbage collection ? • מבני-נתונים מכילים מעגלים: • לכן צריך מימוש מתוחכם יותר