C++

היסטוריה

פיתוח השפה החל בשנת 1979 על ידי בְּיַאנֵה סְטְרוֹוסְטְרוּפ ממעבדות בל AT&T, כאשר הוא החל את עבודתו על הגרסה הראשונה בשמה C with classes (דהיינו "C עם מחלקות"). סטרוסטרופ רצה ליצור שפה חדשה שתשלב את היתרונות של Simula, שפה מונחת עצמים אטית, יחד עם יתרונות היעילות והמהירות של שפת C.
בשנת 1983 זכתה השפה לשימוש במעבדות בל. בשנה זו נוספו לשפה כלים חדשים; בין היתר: פונקציות וירטואליות, העמסת פונקציות, העמסת אופרטורים, הפניות, קבועים, טיפוסיות חזקה והערות שורה (//). באותו זמן קיבלה השפה את שמה החדש ++C. השם בא לבטא את העובדה ש־C++ היא הרחבה לשפת C. פלוס־פלוס הוא אופרטור הגדלה עצמית שקיים ב־C, הוא מגדיל ערך משתנה שלם ב־1. הגרסה המסחרית הראשונה הופצה בשנת 1985 יחד עם פרסום הספר הראשון לשפה מאת סטרוסטרופ, בשם "The C++ Programming Language".
בשנת 1989 עודכנה השפה ונוספו לה תכונות מסוג Protected ו־Static, ואפשרות לירושה מרובה. לאורך השנים נוספו לשפה כלים חדשים, למשל תמיכה בתבניות נוספה רק בשנות ה־90. השפה תוקננה על ידי ארגון התקינה הבינלאומי בשנת 1998, לתקן C++ ISO/IEC 14882:1998. התקן השני לשפה יצא בשנת 2003 בו פורסמו מספר תיקונים לתקן הראשון.
התקן החדש לשפה,C++11, הידוע באופן לא רשמי כ-C++0x פורסם באופן רשמי ב־12 באוגוסט 2011. התקן מגדיר מאפיינים חדשים רבים מאוד לשפה, כולל פונקציות אנונימיות, רשימות אתחול משופרות ואתחול אחיד, ביטויי קבועים דמויי פונקציה, טיפוסים אוטומטיים, לולאות מבוססות טווח (לולאת "foreach"), ועוד מאפיינים רבים מאוד שנדרשו במשך השנים. השפה נמצאת בתהליך פיתוח מתמיד גם כיום.

עקרונות השפה

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

מושגים בשפה

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

מחלקה

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

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

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

הגדרת הקבוע

את הגדרת הקבוע Const ניתן להצמיד למשתנה של מחלקה, לפרמטר של שיטה, לפרמטר של פונקציה, לערך המוחזר מהן וכן לשיטה עצמה. הגדרה זאת מצהירה על כך שאובייקט לא ישתנה אחרי שהוא אותחל. כך מתאפשר למהדר לגלות שגיאות ולייצר קוד יעיל, והמתכנת יכול להיות בטוח שאובייקטים מסוימים לא ישתנו ללא ידיעתו, בקוד שנכתב על ידי מישהו אחר. ההגדרה משתלבת כחלק ממערכת הטיפוסים, ולעיתים מסרבלת את הקוד; סרבול זה עשוי להשתלם אם מדובר בקוד המיועד לשימוש חוזר (Reuse) או אם נדרשת מידה גבוהה של ביטחון בנכונות הקוד, ללא פגיעה בביצועים.
ה־Const מוגדר כ־Bitwise-const - כלומר, המהדר מנסה לוודא שאף ביט של האובייקט לא ישתנה. עם זאת, המצב הרצוי הוא אי שינוי של המצב של האובייקט, אך שינוי של ביטים איננו גורר בהכרח שינוי מצב. כך למשל אובייקט המעוניין לשמור מטמון (Cache) תוך כדי ביצוע של שיטה המוגדרת Const לשם שיפור הריצה בקריאה הבאה, לא יכול לעשות זאת באופן ישיר, כי מבחינת השפה מדובר כאן בשינוי המצב של האובייקט. זאת, אף שאין כל שינוי אמיתי במצב, מבחינת התנהגות האובייקט בעתיד. בתקן החדש ישנו פתרון חלקי לבעיה הזאת. שימוש בהגדרה כ־Mutable מאפשר להגדיר ולשנות משתני מטמון (cache) תוך כדי ביצוע של שיטה המוגדרת Const.
מנגד, על פי הכלל הידוע בשפה לפיו סומכים על המתכנת שהוא יודע מה הוא עושה, הגדרת רפרנס כ־Const עדיין מאפשרת שינוי של האובייקט המסומן, בשימוש במנגנון const_cast.

משתנה ייחוס (Reference variable)

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

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

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

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

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

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

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

תבניות

שפת 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 == 1void foo(){
 int x = Factorial<4>::value; // == 24
 int y = Factorial<0>::value; // == 1}

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

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

קלט ופלט

הקלט והפלט מתבצעים בשפה על ידי Stream־ים מהם מבצעים קריאה באמצעות האופרטור >>, וכתיבה באמצעות האופרטור << . טיפוסים אלו מוגדרים בעיקר באמצעות הספריות iostream (קלט/פלט סטנדרטי) ו־fstream (קלט/פלט באמצעות קבצים) ובמרבית המהדרים על ידי ספריות נוספות.
דוגמה לשימוש בהגדרת הקלט הסטנדרטי cin והפלט הסטנדרטי cout:
int x;cout << "please enter a number: ";cin >> x;
בדוגמה התוכנית תדפיס את הבקשה, ותמתין לקבלת מספר שלם.

Hello World

דוגמה לתוכנית Hello world בשפת C++:
#include <iostream>int main(){
 std::cout << "Hello, world!\n";}