שאלות לראיונות עבודה: Move Semantics ב C++

19/01/2017
C++

הטרולים מקדמים את Qt לעולם של C++11 ו C++14, מה שאומר שאם אתם כותבים Qt אתם נחשפים להמון יכולות חדשות של השפה כולל כחלק מהפלטפורמה. הנה אחת שתפסה את תשומת לבי עד לרמה שהייתי ממליץ לקחת אותה כבסיס לדיאלוג קצר בראיון עבודה.

1. השאלה

האם קיים הבדל, ואם קיים מהו, בין שתי הקריאות:

// 1.
    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();

// 2.
    QProcessEnvironment env(QProcessEnvironment::systemEnvironment());

2. דיון: על מה כדאי להסתכל

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

לכן השאלה הראשונה שתרצו לשאול בתגובה לקוד הנ"ל היא מה הקוד של אופרטור ההשמה ושל בנאי ההעתקה. ביקור קצר בקבצי המקור של Qt יביא לנו את:

QProcessEnvironment::QProcessEnvironment(const QProcessEnvironment &other)
    : d(other.d)
{
}

QProcessEnvironment &QProcessEnvironment::operator=(const QProcessEnvironment &other)
{
    d = other.d;
    return *this;
}

QProcessEnvironment &operator=(QProcessEnvironment && other) Q_DECL_NOTHROW { swap(other); return *this; }

המחלקה מגדירה שני אופרטורי השמה ובנאי Copy Constructor אחד. בשורת הקוד שלנו האוביקט בצד ימין הוא אוביקט זמני שנוצר רק לצורך השמה, ולכן אופרטור ההשמה שיקרא הוא האחרון, אופרטור השמה-הזזה. החל מ C++11 יש לנו אפשרות לזהות מצבים שאופרטור השמה נקרא ב Context בו אין צורך יותר באוביקט בצד ימין, ולטפל בהם אחרת מאשר במצבים בהם האוביקט כן נחוץ.

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

מה לגבי ה Copy Constructor? מסתבר שגם העבודה שלו לא מסובכת. הצצה קצרה בקובץ ה h תראה לנו שהמשתנה d הוא:

private:
    friend class QProcessPrivate;
    friend class QProcessEnvironmentPrivate;
    QSharedDataPointer<QProcessEnvironmentPrivate> d;
};

מאחר ומדובר ב QSharedDataPointer, גם העתקה שלו לא מחייבת הקצאת זכרון. ממילא הזכרון משותף.

3. מסקנה

במקרה שלנו אין הבדל בין הקריאות. בשני המקרים תתבצע הזזה של Pointer יחיד באוביקט החדש שיצביע על המידע שהוקצה פעם אחת בעת יצירת האוביקט הזמני.

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