1.6 - העתקה והעברת בעלות - פתרון
פתרון - משחררים את היעד הקודם¶
הקטע הבא מחליף את main בתוכנית מההרצאה:
int main() {
Number first{11};
Number second{22};
second = std::move(first);
if (first.has_value()) return 1;
if (!second.has_value() || second.value() != 11) return 2;
second = std::move(second);
if (!second.has_value() || second.value() != 11) return 3;
}
בזמן ההשמה משתחררת ההקצאה שהכילה עשרים ושתיים. ההקצאה שהכילה אחת־עשרה עוברת לאחריות second, והמקור מאופס. בסוף main היעד משחרר את אחת־עשרה, והמקור אינו מחזיק דבר למחיקה.
בדיקת self-move אינה משחררת את המשאב במימוש הזה, מפני שכתובות שני האובייקטים זהות. החוזה הזה נובע מהקוד שלנו, ואין להניח שכל טיפוס בספרייה מבטיח בדיוק אותו ערך אחרי self-move.
שלוש גרסאות wrapper¶
בגרסה המקורית מצפים לפלט lvalue ואז rvalue. בקריאה הישירה consume(value) מתקבל lvalue בשני המקרים, מפני ששמו של הפרמטר הוא ביטוי lvalue.
בגרסת consume(std::move(value)) מתקבל rvalue בשני המקרים. עבור int אין כאן משאב שיכול להיגנב, אבל עבור מחלקה בעלת move constructor ה־wrapper היה מאפשר העברה גם מאובייקט שהקורא מסר כ־lvalue.
ההבדל בין forward ל־move הוא ש־forward משחזר מידע שנקבע בזמן הסקת T, ואילו move מציג את האובייקט כמועמד להעברה ללא ההבחנה הזאת.
למה const נשמר¶
הקוד הבא צריך להידחות במחלקה שהעתקתה אסורה:
הביטוי הוא const Number&&, ואינו נקשר לפרמטר Number&& שמאפשר לשנות את המקור. בנאי ההעתקה שיכול היה לקבל const reference נמחק במפורש, ולכן אין מסלול מתאים.
אפשר להוסיף פעולה מפורשת ליצירת בעלים נוסף:
הפעולה מצטרפת למחלקה ודורשת, כמו value, שהאובייקט אינו ריק. היא מקצה משאב חדש, ולכן שני האובייקטים יכולים להימחק בנפרד. שם מפורש כזה מתקשר גם את מחיר ההקצאה ואת העובדה שאין שיתוף של המשאב המקורי.