Menu
Coddy logo textTech

העברה מושלמת

חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 84 מתוך 104.

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

המרכיבים העיקריים הם הפניות מעבירות (נכתבות בתור T&& בהקשר של תבנית) ו־std::forward. הפניה מעבירה יכולה להיקשר הן ל־lvalues והן ל־rvalues, ו־std::forward ממירה את הארגומנט בחזרה לסוג המקורי שלו באופן מותנה:

#include <iostream>
#include <utility>

void process(int& x) { std::cout << "lvalue: " << x << "\n"; }
void process(int&& x) { std::cout << "rvalue: " << x << "\n"; }

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

int main() {
    int n = 10;
    wrapper(n);       // קורא ל־process(int&) - ה־lvalue נשמר
    wrapper(20);      // קורא ל־process(int&&) - ה־rvalue נשמר
}

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

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

challenge icon

אתגר

קל

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

תארגנו את הקוד שלכם בשלושה קבצים:

  • Widget.h: הגדירו מחלקת Widget שעוקבת אחר האופן שבו היא מקבלת את הנתונים שלה.

    המחלקה Widget שלכם צריכה לאחסן שם מסוג std::string וערך מסוג int. ספקו שני בנאים:

    • בנאי שמקבל const std::string& (הפניה לערך l) ו-int: מדפיס Widget constructed (copy): [name]
    • בנאי שמקבל std::string&& (הפניה לערך r) ו-int: מדפיס Widget constructed (move): [name] ומעביר את המחרוזת בהעברה

    הוסיפו גם מתודת display() שמדפיסה [name]: [value].

  • Factory.h: צרו את פונקציית המפעל שלכם להעברה מושלמת.

    כתבו פונקציית תבנית make_object שמקבלת הפניה להעברה עבור השם ו-int רגיל עבור הערך. השתמשו ב-std::forward כדי להעביר את ארגומנט השם לבנאי של Widget תוך שמירה על קטגוריית הערך המקורית שלו.

    הפונקציה צריכה להחזיר את אובייקט ה-Widget שנבנה, לפי ערך.

  • main.cpp: קראו שתי קלטים:
    1. שם (מחרוזת)
    2. ערך (מספר שלם)

    הדגימו העברה מושלמת באמצעות יצירת שני אובייקטים מסוג Widget:

    1. ראשית, אחסנו את השם במשתנה מקומי והעבירו אותו ל-make_object כערך l
    2. שנית, העבירו מחרוזת זמנית (אותו שם בצירוף "_temp") ישירות ל-make_object כערך r

    לאחר יצירת כל אובייקט, קראו למתודת display() שלו.

לדוגמה, עם הקלטים Gadget ו-42:

Widget constructed (copy): Gadget
Gadget: 42
Widget constructed (move): Gadget_temp
Gadget_temp: 42

עם הקלטים Device ו-100:

Widget constructed (copy): Device
Device: 100
Widget constructed (move): Device_temp
Device_temp: 100

התובנה המרכזית היא שפונקציית make_object שלכם לא יודעת אם היא קיבלה ערך l או ערך r, אבל באמצעות שימוש ב-std::forward, היא שומרת על המידע הזה בעת הקריאה לבנאי של Widget. כך פתרון העמסת הבנאים יכול לבחור בגרסה המתאימה, ולאפשר העברות יעילות עבור ערכים זמניים תוך העתקה בטוחה של משתנים בעלי שם.

נסו בעצמכם

#include <iostream>
#include <string>
#include "Widget.h"
#include "Factory.h"

using namespace std;

int main() {
    // קראו את הקלט
    string name;
    int value;
    cin >> name >> value;
    
    // TODO: צרו את הווידג'ט הראשון על ידי העברת name כ-lvalue
    // שמרו את name במשתנה מקומי והעבירו אותו ל-make_object
    
    
    // TODO: קראו ל-display() על הווידג'ט הראשון
    
    
    // TODO: צרו את הווידג'ט השני על ידי העברת מחרוזת זמנית כ-rvalue
    // העבירו את (name + "_temp") ישירות ל-make_object
    
    
    // TODO: קראו ל-display() על הווידג'ט השני
    
    
    return 0;
}
quiz iconבחנו את עצמכם

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

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר C++ אונליין