C++‎

שפת תכנות
(הופנה מהדף C plus plus)
יש לערוך ערך זה. הסיבה היא: ערך מיושן. (פרקים חסרים).
אתם מוזמנים לסייע ולערוך את הערך. אם לדעתכם אין צורך בעריכת הערך, ניתן להסיר את התבנית. ייתכן שתמצאו פירוט בדף השיחה.

C++ (נהגה: "סי פלאס פלאס") היא שפת תכנות מרובת פרדיגמות המבוססת על שפת התכנות C‏, שפותחה בשנות ה־80. C++ מיישמת עקרונות של תכנות פרוצדורלי, תכנות מונחה־עצמים ותכנות גנרי. שפה זו היא מהשפות הפופולריות בקרב מתכנתים בעולם[1], ושפות פופולריות אחרות (כגון Java ו־#C) הושפעו ממנה במידה רבה.

++C
תוכנית Hello world בשפת התכנות C++
תוכנית Hello world בשפת התכנות C++
פרדיגמות תכנות אימפרטיבי, תכנות פרוצדורלי, תכנות מונחה־עצמים, תכנות גנרי
תאריך השקה 1983 עריכת הנתון בוויקינתונים
מתכנן ביארנה סטרוסטרופ
מפתח ביארנה סטרוסטרופ
גרסה אחרונה C++23 (19 באוקטובר 2024) עריכת הנתון בוויקינתונים
טיפוסיות מפורשת, חזקה, סטטית, בטוחה למחצה.
מימושים C++ Builder, clang, Comeau C/C++, GCC, Intel C++ Compiler, Visual C++
הושפעה על ידי C,‏ Simula,‏ Smalltalk, עדה, ‏פסקל, ‏ ALGOL,‏ ML
השפיעה על Java, C#, Perl, Vala
סיומת .C, .cc, .cpp, .cxx, .c++, .h, .H, .hh, .hpp, .hxx, .h++ .cppm, .ixx
isocpp.org
לעריכה בוויקינתונים שמשמש מקור לחלק מהמידע בתבנית

היסטוריה

עריכה

פיתוח השפה החל בשנת 1979 על ידי ביארנה סטרוסטרופ ממעבדות בל AT&T, כאשר הוא החל את עבודתו על הגרסה הראשונה בשמה C with classes (דהיינו "C עם מחלקות")[2]. סטרוסטרופ רצה ליצור שפה חדשה שתשלב את היתרונות של Simula, שפה מונחת עצמים איטית, יחד עם יתרונות היעילות והמהירות של שפת C.

בשנת 1983 זכתה השפה לשימוש במעבדות בל. בשנה זו נוספו לשפה כלים חדשים; בין היתר: פונקציות וירטואליות, העמסת פונקציות, העמסת אופרטורים, הפניות, קבועים, טיפוסיות חזקה והערות שורה (//). באותו זמן קיבלה השפה את שמה החדש ++C[3]. השם בא לבטא את העובדה ש־C++ היא הרחבה לשפת C. פלוס־פלוס הוא אופרטור הגדלה עצמית שקיים ב־C, הוא מגדיל ערך משתנה שלם ב־1. הגרסה המסחרית הראשונה הופצה בשנת 1985 יחד עם פרסום הספר הראשון לשפה מאת סטרוסטרופ, בשם "The C++ Programming Language"[4].

בשנת 1989 עודכנה השפה ונוספו לה תכונות מסוג Protected ו־Static, ואפשרות לירושה מרובה[4]. לאורך השנים נוספו לשפה כלים חדשים, תמיכה בתבניות, למשל, נוספה רק בשנות ה־90. השפה תוקננה על ידי ארגון התקינה הבינלאומי בשנת 1998, לתקן C++ ISO/IEC 14882:1998[5]. התקן השני לשפה יצא בשנת 2003 בו פורסמו מספר תיקונים לתקן הראשון[6].

לשפה יצאו תקנים נוספים בשנים 2011, 2014, 2017, הידועים בשמות C++11, C++14, C++17.

התקן החדש לשפה,C++ 20, הידוע באופן לא רשמי כ-C++2a פורסם באופן רשמי ב־יולי 2019[7]. התקן מגדיר מאפיינים חדשים רבים מאוד לשפה, כולל ranges, ‏פונקציות לחישוב בזמן ריצה ושיפור היכולות של constexpr, ‏concepts, ועוד מאפיינים רבים מאוד שנדרשו במשך השנים. השפה נמצאת בתהליך פיתוח מתמיד גם כיום.

 
ביארנה סטרוסטרופ, יוצר השפה

עקרונות השפה

עריכה

שפת C++ היא שפה מרובת פרדיגמות, כלומר היא משלבת כמה מודלים תכנותיים. השפה תומכת בתכנות פרוצדורלי כמו שפת C, ומוסיפה תמיכה בתכנות מונחה עצמים, תכנות גנרי[8] ובמידת מה אף תכנות פונקציונלי (יצירת פונקציות אנונימיות, העברת פונקציות כפרמטר, ותמיכה במבני נתונים שאינם בני שינוי).

כשפה מונחית עצמים, תומכת C++ בעקרונות כימוס, ירושה ורב־צורתיות (פולימורפיזם). מטרת כלים אלו היא לפשט את מבנה התוכנה, לאפשר שימוש חוזר בחלקי תוכנה קיימים ולהקל על תהליך הפיתוח. שימוש נכון בהם מאפשר לזהות שגיאות כבר בשלב ההידור ולחסוך את הצורך בגילויין ותיקונן בשלבים מאוחרים יותר של תהליך הפיתוח. C++ תומכת בירושה מרובה, המאפשרת למחלקה אחת לרשת משתי מחלקות ויותר, בניגוד לשפות מודרניות אחרות כגון C# או Java. ב־++C קיימים טיפוסים שאינם מחלקות, וניתן לכתוב בה פונקציות שאינן שיטות של מחלקה.

מבחינת התכנות הגנרי, מאפשרת C++ שימוש בתבניות (templates). התבניות מאפשרות כתיבת קוד כללי יעיל, ללא תלות בהיררכיית הורשה. C++ תומכת בתבניות הן לפונקציות והן למחלקות. בנוסף, קיימת טכניקת תכנות בשם "Template Meta-Programming"‏ (TMP) המהווה בעצם "תת־שפה" נפרדת, פונקציונלית, המנצלת את התבניות על מנת לאפשר ביצוע חישובים מורכבים בזמן הידור (בגרסאות מאוחרות של השפה ניתן לבצע חישובים כאלה גם ללא תבניות). בנוסף, שפת ++C ירשה את מנגנון המאקרו של שפת C, על יתרונותיו וחסרונותיו.

++C תוכננה כך שהיא שומרת על תאימות לאחור עם שפת C במידה רבה מאוד[8]. ברוב המקרים, קוד הכתוב בשפת C יהודר וירוץ באותה דרך גם בעזרת מהדר של ++C, בעזרת שינויים מינוריים או ללא שינויים כלל. C++ מתוכננת על מנת לשמור על היעילות והגמישות בהן מצטיינת שפת C, ולכן גם היא מהודרת על פי רוב ישירות לשפת מכונה[8] (זאת בניגוד לשפות #C ו־Java המתורגמות לרוב לשפת ביניים המורצת על ידי מכונה וירטואלית) ומקפידה לאפשר גישה ישירה אל זיכרון המחשב. מנגנונים המוסיפים תקורה מסוימת (לדוגמה חריגות או RTTI) לא פוגעים בזמן הריצה אלא אם כן חל שימוש בהם. עיקרון חשוב בשפה הוא שליטה מלאה של המתכנת על הפעולות שמתבצעות בזמן ריצה. עיקרון זה שולל הפעלה של מנגנון איסוף זבל (אם כי ישנה אפשרות כזאת, בה נעשה שימוש לעיתים רחוקות). תחת זאת, ניהול משאבי מערכת מתבצע לרוב בטכניקה שנקראת RAII, בה ניהול של משאב מתנהל דרך אובייקט המוקדש לכך: "מצביעים חכמים" או אוספים כגון Vector.

השפה כוללת מערכת טיפוסים סטטית מורכבת, שתפקידה לגלות שגיאות בזמן הידור התוכנית ולאפשר יצירת תוכניות יעילות יותר.

מושגים בשפה

עריכה

מרבית המושגים הבסיסיים בשפת C קיימים בצורה זהה או דומה מאוד גם בשפת ++C. להלן תוספות בשפת ++C שאינן קיימות בשפת C.

מחלקה

עריכה

המחלקה (Class) היא לבה של השפה. המחלקה היא תיאור של טיפוס הכולל נתונים ופעולות שאפשר לבצע על הנתונים. משתנים מסוג אותו טיפוס נקרא "עצמים" (Objects). המחלקה כוללת משתנים (Data members) ושיטות (Member functions או Methods). השיטות הן פונקציות שפועלות על המשתנים של המחלקה. שני סוגים חשובים של שיטות הם פונקציות הבנייה (Constructors) ופונקציית ההריסה (Destructor) אשר תפקידן הוא לאתחל עצם מהמחלקה ולמחוק את תוכנו כאשר הוא נמחק.

הגבלת גישה

עריכה

הגבלת הגישה לרכיבי המחלקה השונים מהווה כלי מרכזי למימוש עקרון הכימוס. ישנן שלוש רמות של הגבלות גישה:

  • פרטי (Private) – רק שיטות המחלקה יכולות להשתמש בהם.
  • שמור (Protected) – רק שיטות המחלקה ומחלקות שיורשות ממנה יכולות להשתמש בהם.
  • ציבורי (Public) – לכולם יש גישה אליהם.

כמו כן ניתן להגדיר פונקציה או מחלקה מסוימת כחבר (Friend). חברי המחלקה מקבלים גישה לכל המשתנים, השיטות והטיפוסים שמוגדרים במחלקה, גם לפרטיים וגם לשמורים.

הורשה

עריכה

ניתן להגדיר מחלקה יורשת ("Derived class") על סמך מחלקה אחרת, בסיסית ("Base class"). המחלקה הנורשת מכילה אוטומטית את המשתנים, השיטות ושאר רכיבי המחלקה הבסיסית ובנוסף מגדירה כאלו משל עצמה. לעומת שפות מודרניות אחרות שמתבססות עליה, C++ תומכת בהורשה מרובה, כלומר ירושה ממספר מחלקות בו זמנית.

כאשר מורישים תכונות למחלקה אחת ממחלקה שנייה ניתן לעשות זאת באחת משלוש דרכים:

  • הורשה ציבורית – יורשת את כל המשתנים, השיטות והטיפוסים באותה רמת הגישה.
  • הורשה שמורה – המשתנים, השיטות והטיפוסים הציבוריים של מחלקת האב מקבלים הרשאת גישה שמורה (Protected) במחלקת הבן.
  • הורשה פרטית – המחלקה הנגזרת וחבריה הם היחידים שיכולים להשתמש בתכונות של מחלקת האב. כל התכונות הללו הופכות לפרטיות במחלקת הבן.

ניתן להתייחס לעצם דרך מצביע או ייחוס (Reference) למחלקה בסיסית, ולהפעיל שיטות של העצם בלי לדעת מראש את הטיפוס המדויק שלו. שיטות כאלו מוגדרות "וירטואליות". הן ניתנות להפעלה על ידי התייחסות לעצם ממחלקה בסיסית אך בפועל מבוצעת המתודה במחלקה הנורשת, בהתאם לסוג העצם עבורו הופעלה המתודה. זהו סוג מסוים של פולימורפיזם (הסוג האחר הוא תכנות גנרי בעזרת תבניות – ראה בהמשך).

מנגנון ה־Run-Time Type Information‏ (RTTI) מאפשר לקבל מידע על הטיפוס של העצם הנתון, תוך כדי ריצת התוכנית. מנגנון זה מגדיר את האופרטור dynamic_cast שמאפשר לבצע המרה בטוחה בין מצביע (או הפנייה) למחלקת בסיס לבין מצביע למחלקה הנגזרת. אופרטור זה מאפשר לבדוק האם העצם הנתון הוא מטיפוס מסוים או לא. אופרטור נוסף שנוסף לשפה במסגרת ה־RTTI הוא אופרטור ה־typeid. אופרטור זה מאפשר לקבל את הטיפוס המדויק של העצם הנתון.

לעומת שפות אחרות, כמו C# לדוגמה, ב־C++ אין הבדל בין מחלקות (class) לבין מבנים (struct). גם מחלקות וגם מבנים יכולים להכיל שיטות, לרשת האחד מהשני, להגדיר פונקציות וירטואליות ולהגדיר רמות גישה שונות. ההבדל היחיד הוא שתכונות המבנה מוגדרות ציבוריות כברירת מחדל ואילו תכונות המחלקה כפרטיות.

טיפוסיות חזקה

עריכה

מערכת הטיפוסים בשפה חזקה יותר מזו שבשפת C. בשפת C, המהדר מבצע המרות טיפוסים בצורה אוטומטית ובקלות יחסית, ובה ניתן להשתמש במצביע *void שיכול להצביע אל אובייקט מכל טיפוס.

בשפת C++ הוקשחו הכללים והמרות טיפוסים מובלעות נעשות רק לפי הגדרות המובנות בשפה או כאלה שהוגדרו על ידי המשתמש. שימוש ב-*void דורש המרה מפורשת, ולכל סוג של המרה ישנו אופרטור מפורש מתאים (static_cast, dynamic_cast, const_cast, reinterpret_cast). תכונה זו מאפשרת גילוי שגיאות רבות יותר בשלב ההידור וחוסכת את מציאתן המייגעת בזמן הריצה.

בשל הגישה הכללית של השפה לאפשר למתכנת לבצע כל פעולה, בתנאי שברור שהוא באמת מעוניין בכך, הטיפוסיות בשפה בטוחה פחות מאשר בשפת Java, למשל.

הגדרת הקבוע

עריכה

את הגדרת הקבוע Const ניתן להצמיד למשתנה של מחלקה, לפרמטר של מתודה או פונקציה, לערך המוחזר מהן וכן למתודה עצמה. הוספת ה-qualifier למשתנה מצהירה על כך שהמשתנה לא ישתנה אחרי שהוא אותחל. כך מתאפשר למהדר לגלות שגיאות ולייצר קוד יעיל, ומונע מהמשתמש לשנות אובייקטים בטעות. הצמדה של ה-qualifier למתודה, מצהירה על כך שהמתודה הזאת יכולה להיקרא מתוך אובייקט של המחלקה שהוגדר כ-const.

ה־Const מוגדר היטב עבור טיפוסים סקלריים, אך עבור מחלקות הוא עשוי שלא להיות מספק, וזאת מכיוון שהמהדר מוודא שהבתים בזיכרון ששייכים לאובייקט לא משתנים אך אינו בודק דבר פרט לזה. בעיות אפשריות שיכולות לנבוע מכך כוללות:

  1. אובייקט שמחזיק מצביע, ומשנה את הערך אליו הוא מצביע.
  2. אובייקט שמחזיק משתנים שאינם משפיעים על הממשק שהמחלקה מייצאת, אלא רק על המימוש הפנימי. דוגמאות נפוצות לכך הן cache ו-mutex.

פתרון לבעיה הראשונה הוא עקרון הכימוס - המחלקה תחשוף בממשק שלה עבור אובייקטים קבועים אך ורק מתודות שלא משנות את נתוני המחלקה.

פתרון לבעיה השנייה הוא ה-qualifier ‏Mutable, שמאפשר שינוי של משתנים באובייקט של המחלקה גם כאשר האובייקט מסומן כ-const. כעיקרון תכנותי, מוצע כלל M&M ‏ – "Mutable and Mutex go together".

הצוויה (Reference)

עריכה

ניתן להגדיר משתנים, פרמטרים וערכים מוחזרים כמתייחסים לעצם (Reference). הייחוס דומה מאוד להצבעה: כמה משתנים יכולים להתייחס לאותו עצם ולפעול עליו במשותף, כמו שמצביעים שונים יכולים לפעול על אותו עצם. ישנם מספר הבדלים מהותיים בין מצביע לייחוס: מצביע יכול להיות לא מאותחל, ואילו ייחוס תמיד יתייחס לעצם כלשהו. כמו כן ניתן לשנות הצבעה של מצביע אבל לא ניתן לשנות ייחוס, הייחוס נקבע לכל אורך חייו של הייחוס. מבחינה תחבירית השימוש בייחוס הוא כמו בעצם עצמו.

מערכת החריגות

עריכה

בשפה קיימת מערכת חריגות המאפשרת לטפל בשגיאות שנוצרות במהלך ריצת התוכנית. זריקה וטיפול בשגיאות מתבצעת באמצעות שלוש מילים שמורות: throw, try ו-catch.

המילה throw גורמת לזריקת שגיאה, אם הזריקה לא תתפס, הטיפול בשגיאה יעבור למערכת ההפעלה שלרוב תקרא לפרוצדורת טיפול שתסגור את התכנית.

כחלק מזריקת השגיאה מצרפים משתנה כלשהו שלרוב נועד כדי לזהות את השגיאה. בעוד השפה תומכת בזריקת כל סוגי הטיפוסים הקיימים, הקונבנציה היא לזרוק אך ורק אובייקטים שיורשים מהטיפוס std::exception.

int num_of_students;
std::cin >> num_of_students;

if(num_of_students < 0)
{
    throw std::exception ("the num cant be less then 0");
    // throw num_of_students; would have worked as well
}

המילה הזו גורמת לכך שכל מה שיהיה בשטח האתחול (סקופ - {}) שאחריו יהיה תחת "השגחה", אם תיזרק שגיאה התכנית "תדע" שהיא צריכה לבדוק את הcatch שאחריו.

הtry והcatch חייבים להיות צמודים.

try
{
    int num_of_students;
    std::cin >> num_of_students;

    if(num_of_students < 0)
    {
        std::exception e("the num cant be less then 0");
        throw e;
    }
}

המילה השמורה catch "תופסת" את השגיאות שנזרקו בcatch שלפניה לפי סוג המשתנה הנזרק, לכל סוג משתנה צריך להיות שדה catch משלו. כאשר תופסים אובייקט by-reference, ניתן להשתמש במערכת הירושה של C++ על מנת לקבל טיפוסים מסוגים שונים. לעיתים, אנחנו רוצים להשתמש בתפיסה פולימורפית אך גם בספציפיקציה שלה. לדוגמה – תפיסה של כל ה-std::exception היא פעולה נפוצה, אבל לעיתים נרצה לבצע פעולה שונה כאשר נזרק std::system_error, שמתאר לרוב שגיאה של syscall. במקרה זה, חובה לתפוס את המחלקה הספציפית – במקרה שלנו, std::system_error קודם, מכיוון שמערכת השגיאות מחפשת את ההתאמה הראשונה האפשרית.

ניתן לכתוב catch התופס את כל סוגי השגיאות שנזרקו, על ידי שימוש בטיפוס ה"אליפסה" של C/C++ – .... בתוך הcatch יהיה אופן הטיפול בשגיאה כזו.

catch (std::system_error& e) {
    std::cerr << "System error occurred! " << e.what() << " error code: " << e.code() << std::endl;
} catch (std::exception& e) {
    std::cerr << "An error occurred: " << e.what() << std::endl;
} catch (...) {
    std::cerr << "An unknown exception was raised" << std::endl;
}

לעיתים, מטרת התפיסה היא לא על מנת להמשיך את התכנית כרגיל, אלא על מנת לכתוב לוגים וטלמטריה, ולאחר מכן להמשיך את תהליך זריקת השגיאה. השפה תומכת במנגנון שנפוץ ברוב השפות עם מערכת שגיאות שנקרא rethrow – כלומר, המשך תהליך של ה-error handling כאילו השגיאה לא נתפסה. הפקודה שמבצעת rethrow היא המילה השמורה throw ללא פרמטרים בכלל אחריה.

ההבדל בין rethrow לבין זריקה מחדש של השגיאה הוא קטן אך קיים. לאובייקט שגיאה לרוב יש מידע ספציפי למערכת הפעלה שבה השגיאה נזרקה – כלומר, תמונה של הרגיסטרים בעת השגיאה, קוד שגיאה ספציפי למערכת הפעלה וכדומה. כאשר נזרוק שגיאה מחדש, כלומר נכתוב throw e כאשר e זאת השגיאה שנתפסה, האובייקט הספציפי למערכת הפעלה יישתנה על מנת לתאר את מיקום השגיאה החדש, בעוד כאשר נשתמש במנגנון ה-rethrow, המערכת תמשיך עם האובייקט הישן.

הבדל נוסף הוא זריקה מחדש מתוך catch כללי, בו אין שם למשתנה, ושם ניתן לזרוק שגיאה חדשה לחלוטין או לבצע rethrow.

קיים גם הבדל הסמנטי בין throw חדש לבין rethrow. אם התגלתה שגיאה חדשה במהלך הטיפול בשגיאה הישנה, נבצע throw חדש על השגיאה החדשה, ואולי נצרף אליה מידע מהשגיאה הישנה. לעומת זאת, אם לא ניסינו לטפל בשגיאה הישנה או שניסינו ולא הצלחנו, אז נבצע rethrow על מנת ש-catch נמוך יותר בהיררכיה ינסה לטפל בשגיאה.

catch (std::system_error& e) {
    // Throwing a new exception, with the same object
    throw e;
} catch (std::exception& e) {
    // Rethrow semantics
    throw;
} catch (...) {
    // Rethrow. C++ supports capturing the error object of ... using
    //  the os-specific object std::current_exception()
    throw;
}

ביטויי למבדה (פונקציות אנונימיות)

עריכה

ביטוי למבדה הוא דרך להתייחס לפונקציה כאל עצם, באפשרותו אפשר ליצור מצביעים לפונקציות.

ביטוי למבדה נכתב בצורה הבאה:

{האלגוריתם עצמו} (המשתנים שהפונקציה מקבלת) [סוג המשתנים שהפונקציה תירש, ציבורי / שמור / פרטי]

העמסת פונקציות

עריכה

העמסת פונקציות היא היכולת להשתמש באותו השם לפונקציות (או שיטות) שונות, בתנאי שישנו הבדל ביניהן במספר הפרטרים או בטיפוסי הפרמטרים אותם הן מקבלות. הדבר מאפשר להגדיר מספר רב של פונקציות שמבחינה לוגית מבצעות פעולה דומה אך מקבלות פרמטרים שונים. תכונה זו מאפשרת לכתוב קוד קריא יותר, אם כי הגזמה בהעמסת פונקציות עשויה ליצור אי־בהירות או מקרים של דו־משמעות.

פרמטרים אופציונליים

עריכה

דרך נוספת להעמיס פונקציות היא להגדיר ערכי ברירת מחדל לפרמטרים של מתודה או פונקציה. אם מתבצעת קריאה לפונקציה שאיננה כוללת ערך עבורם, הם יקבלו את ערך ברירת המחדל. כך ניתן בקלות להגדיר פעולה זהה עבור מספר שונה של פרמטרים, מבלי לבצע שכפולי קוד.

כאמור, ניתן לכתוב ב-++C את כל הקשת, מתכנות פרוצדורלי עם טיפוסיות חלשה בדומה לשפת C ועד לניצול פרדיגמות התיכנות המודרניות ביותר. מסיבה זו ניתן, אך לא מומלץ, לכתוב פונקציות המקבלות מספר בלתי מוגדר מראש של פרמטרים, על ידי שימוש בשלוש נקודות (...) בחתימת הפונקציה – נקרא Ellipsis parameter. תכונה זו קיימת רק על מנת להקל על הגירה של תוכניות משפת C לשפת ++C. את ה־Printf של C, שהיא דוגמה לפונקציה עם מספר פרמטרים משתנה (...), מחליפות ב-++C מחלקות כגון iostream המאפשרות לשרשר פרמטרים להדפסה תוך שימוש בהעמסת אופרטורים וטיפוסיות חזקה. במידה ומשתמש רוצה לקבל מספר לא ידוע של משתנים לתוך פונקציה, C++ מאפשרת העברת Parameter pack.

העמסת אופרטורים

עריכה

בשפה קיים מנגנון העמסת אופרטורים. כלומר, ניתן להגדיר מחדש כמעט כל אופרטור זמן ריצה המוגדר בשפה כך שהוא יהיה בעל משמעות עבור טיפוסים שונים, כולל טיפוסים המוגדרים על ידי המשתמש. למשל, אופרטור החיבור (+) בצורתו הרגילה מאפשר חיבור שני משתנים המכילים ערך מספרי. אך ניתן להגדיר אותו כך שיהיה בעל משמעות גם עבור משתנים מטיפוס שבר שהוגדר על ידי המתכנת.

אין כל הבדל סמנטי בין העמסת אופרטורים להעמסת פונקציות, שכן ההבדל בין אופרטורים לפונקציות הוא תחבירי בלבד. שימוש בהעמסה עשוי לשפר את קריאות הקוד, ושימוש מוגזם עשוי להפוך קוד לבלתי קריא.

תבניות (Templates)

עריכה

שפת C++ תומכת ב־Templates – תבניות. התבניות מהוות רמת הפשטה נוספת מעל רמת ההפשטה של המחלקה. הן מאפשרות יצירת מחלקות או פונקציות על ידי תבנית, כאשר בכל פעם הפונקציה או מחלקה נוצרת עבור טיפוס או טיפוסים אחרים. ה"טיפוס" הוא טיפוס פשוט או מחלקה בעצמו. הטיפוסים "מועברים" בכמעין פרמטר על ידי שימוש בסוגריים זוויתיים: "<...>" במקום השימוש ב "(...)" הרגילים. המהדר מזהה אילו פרמטרים הועברו לתבנית ומשכפל את התבנית בהתאם.

Template Metaprogramming

עריכה

ישנה טכניקת תכנות בשם "Template Metaprogramming" או בקיצור TMP, המנצלת את התבניות על מנת לבצע חישובים מורכבים בזמן ההידור, ובכך עשויה להפחית את החישובים שיש לבצע בזמן ריצה.

טכניקה זו נתגלתה במקרה: במהלך הכנת התקן של שפת ++C, התברר שהתבניות של השפה הן בעצמן תת־שפה שלמה טיורינג, כלומר שניתן לבצע באמצעותה כל חישוב שיכול מחשב לבצע. הדוגמה הראשונה לתוכנית כזאת ביצעה חישוב של מספרים ראשוניים על אף שלא סיימה לעבור הידור. הרשימה של המספרים הראשוניים הייתה חלק מהודעת השגיאה שהפיק המהדר בזמן שניסה להדר את הקוד.

כיום ישנן ספריות רבות המסייעות לעבודה בטכניקה זו, אך כיוון שמלכתחילה לא הייתה כוונה ליצור את תת־השפה הזאת בתוך ++C, התחביר שלה קשה להבנה. להלן דוגמה לקטע קוד שמבצע חישוב של עצרת:

template <int N>
struct Factorial
{
 enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0>
{
 enum { value = 1 };
};

// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
 int x = Factorial<4>::value; // == 24
 int y = Factorial<0>::value; // == 1
}

ספרית התבניות התקנית

עריכה

ספריית התבניות התקנית (STL – Standard Template Library) מכילה מימושים יעילים של מבני נתונים רבים כגון (מחסנית, רשימה מקושרת, עץ חיפוש מאוזן ועוד), וכן טיפוסי נתונים סטנדרטיים חשובים כמו וקטור (מערך דינמי) ומחרוזת. הספרייה גם מכילה אלגוריתמים גנריים שניתן להפעיל על מבני הנתונים כמו גם מחלקות לטיפול במספרים ובקלט־פלט. הארכיטקטורה של הספרייה מאפשרת להרחיב אותה בקלות יחסית. ניתן להוסיף אלגוריתם חדש שפועל על מבני הנתונים הקיימים ומבנה נתונים חדש שהאלגוריתמים הקיימים עובדים עליו.

תוספות אלו, תורשה מרובה (1989), העמסת אופרטורים (1989), תבניות (1991), ספרית התבניות הסטנדרטית ו־(RTTI (1995 הוכנסו לשפה בהדרגה.

קלט ופלט

עריכה

הספרייה הסטנדרטית של C++ מציעה אפשרות של ביצוע קלט\פלט באמצעות Stream-ים מהם מבצעים קריאה באמצעות האופרטור >>, וכתיבה באמצעות האופרטור <<. טיפוסים אלו מוגדרים בעיקר באמצעות הספריות iostream (קלט/פלט סטנדרטי) ו־fstream (קלט/פלט באמצעות קבצים). למעשה, אופן עיבוד קלט ופלט מתוך מחרוזת בתים נעשה על ידי המחלקה std::ios_base, מחלקה מעין-אבסטרקטית שאי אפשר ליצור באופן ישיר. מתחת למחלקה ios_base, קיימת המחלקה התבניתית (טמפלייטית) basic_ios, שנועדה לטפל ב-stream של תווים גנריים. דוגמה לכך תהיה האובייקט std::wfstream שנועד לטפל בקבצים עם תווי יוניקוד.

ארכיטקטורה

עריכה

הארכיטקטורה של ios נחשבת לארכיטקטורה מאוד שנויה במחלוקת ועושה שימוש נרחב בתכונות של השפה שנחשבות כ-anti pattern, כגון ירושה מרובה, שדות ופעולות מסוג protected וירושה וירטואלית. המבנה הבסיסי של אובייקט קלט פלט מורכב מאובייקט אבסטרקטי מסוג buffer – אובייקט מסוג std::basic_streambuf, שאמור לתאר את הממשק הגנרי שמקבלים מתוך אובייקט שמדמה buffer. באובייקט buffer הלרוב בלתי נראה משתמשים בתוך basic_ios על מנת לבצע פעולות קלט ופלט מפורמטות. מימוש נכון של buffer מעל אובייקטי קלט/פלט לא סטנדרטיים כגון pipe או socket יאפשרו למתכנת להשתמש בכל צורות ה-Formatting של std.

חיסרון מובנה של אובייקטים גנריים של C++ הוא חוסר היכולת להשתמש בתכולות ספציפיות למערכות הפעלה. לדוגמה, במערכת ההפעלה חלונות ניתן לבצע פעולות אסינכרוניות על קבצים על ידי שימוש במבנה OVERLAPPED, וסיום של פעולה כזו מסומן על ידי Event, אובייקט ייחודי ל-Windows. לעומת זאת, במערכות מבוססות Linux, ניתן לקבל יכולת דומה על ידי המבנה aiocb ומנגנון הסיגנלים הייחודי למערכות Unix. בהיעדר שיטה חסכונית המתארת את שתי הפעולות בצורה אחידה, נכון לשנת 2023, בחרו לא לאפשר ל-Bufferים לבצע פעולות אסינכרוניות כחלק מהממשק הסטנדרטי.

עקב הסיבוך הרב הכרוך המימושם, עיקר השימוש בממשקי ה-IO הסטנדרטיים נעשה דרך עטיפות של STL, שלא בהכרח מכבדות את כל התכולות הניתנות. דוגמה לכך היא המחלקה std::basic_fstream, שמחזיקה מצביע אחד גם לקריאה ולכתיבה יחדיו, ושינוי של אחד ישנה את השני. הסיבה להתעלמות מהתכולה המאפשרת להחזיק שני מצביעים שונים נובעת ככל הנראה מאחר שרוב מערכות ההפעלה המוכרות מחזיקות מצביע אחד עבור קובץ.

דוגמת שימוש בסיסי

עריכה
int x;

std::cout << "please enter a number: ";
std::cin >> x;

בדוגמה התוכנית תדפיס את הבקשה, ותמתין לקבלת מספר שלם.

Hello World

עריכה

דוגמה לתוכנית Hello world בשפת C++:

#include <iostream>

int main()
{
    std::cout << "Hello, world!" << std::endl;
    return 0;
}

ראו גם

עריכה

לקריאה נוספת

עריכה
  • Stroustrup, Bjarne (2000). The C++ Programming Language, Special Edition, Addison-Wesley. ISBN 0-201-70073-5.
  • Stroustrup, Bjarne (1994). The Design and Evolution of C++. Addison-Wesley. ISBN 0-201-54330-3.
  • Meyers Scott (2005). Effective C++: 55 Specific Ways to Improve Your Programs and Designs, 3rd Edition, Addison-Wesley. ISBN 0-321-33487-6.
  • Meyers Scott (1995). More Effective C++: 35 New Ways to Improve Your Programs and Designs, Addison-Wesley. ISBN 0-201-63371-X.

קישורים חיצוניים

עריכה

הערות שוליים

עריכה
  1. ^ אינדקס "TIOBE"
  2. ^ Stroustrup, Bjarne (7 במרץ 2010). "C++ Faq: When was C++ Invented". ATT.com. אורכב מ-המקור ב-2011-09-26. נבדק ב-26 במאי 2012. {{cite web}}: (עזרה)
  3. ^ Bjarne Stroustrup, A History of C++: 1979−1991, p.17
  4. ^ 1 2 History of C++ - C++ Information באתר cplusplus.com
  5. ^ ISO/IEC 14882:1998 - Programming languages -- C++ באתר ארגון התקינה הבינלאומי
  6. ^ ISO/IEC 14882:2003 - Programming languages -- C++ באתר ארגון התקינה הבינלאומי
  7. ^ Bhagyashree R, ISO C++ Committee announces that C++20 design is now feature complete, Packt Hub, ‏2019-02-25 (באנגלית)
  8. ^ 1 2 3 A brief description - C++ Information באתר cplusplus.com