לדלג לתוכן

1.3 - ערכים זמניים וחיי אובייקטים - פתרון

פתרון - תחום הבלוק אינו רק נראות של שם

התוכנית הבאה מאפשרת לצפות ב־return מוקדם ובסדר ההשמדה:

#include <cstdio>

struct Trace {
    const char* name;
    explicit Trace(const char* n) : name(n) {
        std::printf("+ %s\n", name);
    }
    ~Trace() {
        std::printf("- %s\n", name);
    }
};

void run(bool early) {
    Trace outer{"outer"};
    {
        Trace first{"first"};
        Trace second{"second"};
        if (early) return;
        std::puts("inner work");
    }
    std::puts("outer work");
}

void inspect(const Trace& item) {
    std::printf("inspect %s\n", item.name);
}

int main() {
    run(true);
    inspect(Trace{"temporary"});
    std::puts("after");
}

הקריאה עם true מדפיסה בנייה של outer, אחריו first ואחריו second. בזמן ה־return מתבצעת השמדה בסדר הפוך: השני, הראשון והחיצוני. שתי הדפסות העבודה אינן מתבצעות, אבל יציאה מוקדמת רגילה אינה מדלגת על השמדת האובייקטים המקומיים שנבנו.

הקריאה ל־inspect יוצרת temporary, משתמשת בו ומסיימת את חייו לפני ההדפסה after. שמירת מצביע לתור שמופעל מאוחר יותר הייתה משאירה alias שאינו בעלים; במקרה הזה חיי המקור קצרים מחיי העבודה המתוכננת.

שתי אפשרויות להחזיר תוצאה

החזרת ערך נותנת לקורא אובייקט תוצאה עצמאי:

#include <string>

std::string make_title() {
    return "job";
}

ממשק שממלא אובייקט של הקורא נראה כך:

void fill_title(std::string& output) {
    output = "job";
}

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

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

סדר השדות

המחלקה הבאה מדגימה סדר רשימת אתחול מטעה:

struct Pair {
    Trace first;
    Trace second;

    Pair() : second("second"), first("first") {}
};

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

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

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