אידיום Pimpl
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 78 מתוך 104.
אידיומת Pimpl (מצביע למימוש) היא טכניקה שמסתירה את פרטי המימוש של מחלקה על ידי העברתם למחלקה נפרדת שהוצהרה מראש. כך מצטמצמות התלויות בקומפילציה, והחברים הפרטיים נשארים מוסתרים באמת מקובץ הכותרת.
הרעיון המרכזי פשוט: במקום להצהיר על איברים פרטיים ישירות במחלקה שלך, אתה מצהיר על מצביע למחלקת מימוש שמוגדרת רק בקובץ המקור:
// Widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget();
void doSomething();
private:
class Impl; // הצהרה מקדימה
std::unique_ptr<Impl> pImpl;
};// Widget.cpp
#include "Widget.h"
#include <iostream>
class Widget::Impl {
public:
int data = 42;
void process() { std::cout << "Processing: " << data << "\n"; }
};
Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;
void Widget::doSomething() { pImpl->process(); }היתרונות העיקריים הם חומת הגנה מפני קומפילציה ותאימות בינארית. כשמשנים את המחלקה Impl, צריך לקמפל מחדש רק את קובץ המקור — ולא כל קובץ שכולל את קובץ הכותרת. הדבר מקצר משמעותית את זמני הבנייה בפרויקטים גדולים.
שימו לב שיש להגדיר את ההורס בקובץ המקור (גם אם הוא מוגדר כברירת מחדל), כי unique_ptr זקוק לטיפוס המלא של Impl כדי למחוק אותו. זוהי מלכודת נפוצה בשימוש ראשוני ב־Pimpl.
אתגר
קלבואו נבנה מטפל בהודעות מאובטח באמצעות אידיום Pimpl, כדי להסתיר את פרטי המימוש של ההצפנה מקובץ הכותרת. כך נמחיש כיצד Pimpl יוצר חומת אש לקומפילציה: מי שיכלול את קובץ הכותרת שלך לא יוכל לראות את המימוש הפנימי של עיבוד ההודעות.
תארגן את הקוד שלך בשלושה קבצים:
SecureMessage.h: הגדר את הממשק הציבורי של המחלקהSecureMessageשלך.המחלקה שלך צריכה לכלול מחלקת
Implשהוכרזה בהכרזה מקדימה ו-std::unique_ptrאליה. הממשק הציבורי צריך לכלול:- בנאי שמקבל
const std::string&עבור ההודעה המקורית - הורס (חובה להצהיר עליו כאן ולהגדיר אותו בקובץ .cpp)
- מתודת
setKey(int key)להגדרת מפתח הצפנה - מתודת
getEncrypted()שמחזירה את ההודעה המוצפנת בתורstd::string - מתודת
getOriginal()שמחזירה את ההודעה המקורית
קובץ הכותרת צריך להציג רק את הממשק הציבורי: פרטי המימוש של אופן פעולת ההצפנה לא צריכים להופיע בו.
- בנאי שמקבל
SecureMessage.cpp: הגדר את המחלקה המקוננתImplוממש את כל המתודות.המחלקה
Implשלך צריכה לאחסן את ההודעה המקורית ואת מפתח ההצפנה (ערך ברירת המחדל הוא 0), ולטפל בלוגיקת ההצפנה בפועל. השתמש בצופן קיסר פשוט להצפנה: הזז כל תו לפי ערך המפתח. לדוגמה, עם מפתח 3, 'a' הופך ל-'d', ו-'z' חוזר להתחלה והופך ל-'c'.ההצפנה צריכה להשפיע רק על אותיות קטנות (a-z), ולהשאיר את כל התווים האחרים ללא שינוי. זכור להגדיר כאן את ההורס (גם אם הוא מוגדר כברירת מחדל), מכיוון ש-
unique_ptrזקוק לטיפוסImplמלא.main.cpp: קרא שני קלטים:- מחרוזת הודעה (עשויה להכיל רווחים)
- מפתח הצפנה (מספר שלם)
צור אובייקט
SecureMessage, הגדר את המפתח והצג את התוצאות:- הדפס
Original:ואחריו ההודעה המקורית - הדפס
Encrypted:ואחריו ההודעה המוצפנת
לדוגמה, עם הקלטים hello world ו-3:
Original: hello world
Encrypted: khoor zruogעם הקלטים xyz abc ו-5:
Original: xyz abc
Encrypted: cde fghשים לב שקובץ הכותרת לא חושף דבר על המימוש של צופן קיסר: זהו כוחו של Pimpl. אם בהמשך תחליף את אלגוריתם ההצפנה באלגוריתם אחר, רק SecureMessage.cpp יצטרך הידור מחדש, ולא אף קובץ שכולל את קובץ הכותרת.
נסו בעצמכם
#include <iostream>
#include <string>
#include "SecureMessage.h"
int main() {
// קרא את ההודעה (עשויה להכיל רווחים)
std::string message;
std::getline(std::cin, message);
// קרא את מפתח ההצפנה
int key;
std::cin >> key;
// TODO: צור אובייקט SecureMessage עם ההודעה
// TODO: הגדר את מפתח ההצפנה
// TODO: הדפס "Original: " ולאחר מכן את ההודעה המקורית
// TODO: הדפס "Encrypted: " ולאחר מכן את ההודעה המוצפנת
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סיכום — מחשבון צורות11מושגי OOP מתקדמים
הרכבה לעומת הורשהMixins באמצעות CRTPאידיום Pimplמחיקת טיפוסיםמחלקות Enum וטיפוסיות חזקהטיפול בחריגות ב-OOPהיררכיות חריגות מותאמות אישית3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין