انتقل إلى المحتوى

المراجع: Lvalue، Rvalue والمزالق

استكشف مراجع lvalue وrvalue، وافهم فئات قيم C++، وتعلم قواعد طي المراجع وإعادة توجيه المراجع، وتجنب الأخطاء المرجعية المتدلية.

مراجع القيمة

مرجع القيمة (T&) هو اسم مستعار — اسم آخر لكائن موجود. على عكس المؤشرات، يجب تهيئة المراجع، ولا يمكن أن تكون فارغة، ولا يمكن إعادة وضعها للإشارة إلى كائن مختلف.

ترتبط مراجع Lvalue بـ lvalues — التعبيرات التي لها هوية (يمكنك أخذ عنوانها). المتغيرات وعناصر المصفوفة والمؤشرات التي تم إلغاء الإشارة إليها كلها قيم. لا يمكن ربط مرجع lvalue غير ثابت (int&) بـ مؤقت (rvalue) لأن تعديل مؤقت يكون دائمًا خطأً.

أساسيات مرجع Lvalue

تعد مراجع Lvalue هي العمود الفقري لـ C++ - فهي تستخدم للتمرير المرجعي والتحميل الزائد للمشغل وتجنب النسخ.

lvalue_refs.cpp
#include <iostream>
#include <string>

void append_greeting(std::string& s) {
    s += ", welcome!";  // modifies the caller's string
}

int main() {
    int x = 10;
    int& ref = x;      // ref is an alias for x
    ref = 20;
    std::cout << x << '\n';  // 20 — x was modified through ref

    // const reference can bind to a temporary (rvalue)
    const std::string& greeting = std::string("Hello");
    // The temporary's lifetime is extended to match greeting's scope
    std::cout << greeting << '\n';  // "Hello"

    // Non-const lvalue reference CANNOT bind to rvalue:
    // std::string& bad = std::string("oops");  // COMPILER ERROR

    std::string name = "Alice";
    append_greeting(name);
    std::cout << name << '\n';  // "Alice, welcome!"
}

فئات القيمة في C++

يحتوي C++ على تصنيف دقيق لفئات قيمة التعبير. يعد فهمها أمرًا ضروريًا لدلالات النقل وإعادة التوجيه المثالية:

- lvalue — له هوية، ولا يمكن نقله ضمنيًا. أمثلة: المتغيرات،*ptr،arr[i]، سلسلة حرفية.
- prvalue (rvalue خالص) — ليس له هوية، ويمكن النقل منه. أمثلة:42،x + y،std::string("temp")، الدالة التي تُرجع حسب القيمة.
- xvalue (قيمة منتهية الصلاحية) — لها هوية ويمكن النقل منها. أمثلة:std::move(x)،static_cast(x)، عضو في قيمة r.
- glvalue (lvalue المعمم) — lvalue أو xvalue (له هوية).
- rvalue — prvalue أو xvalue (يمكن النقل منها).

الفكرة الأساسية: المتغير المرجعي rvalue هو بحد ذاته lvalue — وله اسم وعنوان. هذا هو مصدر الكثير من الالتباس وهو بالتحديد سبب وجودstd::forward.

Rvalue References (T&&)

Rvalue references bind to rvalues (temporaries andstd::move'd objects). They are the foundation of move semantics — they let you detect when an object is about to be destroyed and steal its resources.

rvalue_refs.cpp
#include <iostream>
#include <string>
#include <utility>  // std::move

void process(const std::string& s) {
    std::cout << "lvalue: " << s << '\n';
}

void process(std::string&& s) {
    std::cout << "rvalue: " << s << '\n';
    // s is an lvalue inside this function (it has a name!)
    // To move from s, you'd need std::move(s)
}

int main() {
    std::string a = "hello";
    process(a);               // calls lvalue overload
    process(std::string("world"));  // calls rvalue overload
    process(std::move(a));    // calls rvalue overload — a is now moved-from

    // IMPORTANT: a is in a valid but unspecified state
    // Only safe operations: assign to it, destroy it
    std::cout << "a after move: '" << a << "'\n";  // likely empty
}

Reference Collapsing & Forwarding References

When references combine in templates, reference collapsing rules apply:

-T& &collapses toT&
-T& &&collapses toT&
-T&& &collapses toT&
-T&& &&collapses toT&&

The rule is simple: if either reference is an lvalue reference, the result is an lvalue reference. Only&& &&gives&&.

A forwarding reference (also called universal reference) isT&&whereTis a deduced template parameter. It can bind to both lvalues and rvalues.auto&&is also a forwarding reference. Butstd::vector&&is NOT —Tis already known.const T&&is also not a forwarding reference.

خطأ شائع

A dangling reference refers to a destroyed object. Unlike dangling pointers, there is nonullptrcheck — the code will silently use garbage data or crash.

Common causes:

- Returning a reference to a local variable — the variable is destroyed when the function returns.
- Capturing by reference in a lambda that outlives the referenced variable.
- const auto&to a temporary inside an expression —const auto& val = get_map()["key"]is dangling ifget_map()returns by value, because the temporary map is destroyed at the semicolon.
- Range-based for loop over a temporary —for (auto& x : get_vector())is fine (temporary lives for the loop), butfor (auto& x : get_wrapper().get_vector())can dangle if the wrapper is destroyed.

تمديد مدى الحياة مع ثابت T&

Aconstlvalue reference can extend the lifetime of a temporary, but only when directly bound. This does NOT work through function calls or member access chains.

lifetime_extension.cpp
#include <iostream>
#include <string>

std::string make_greeting() { return "Hello, World!"; }

const std::string& identity(const std::string& s) { return s; }

int main() {
    // SAFE: const ref directly bound to temporary
    const std::string& good = make_greeting();
    std::cout << good << '\n';  // OK — lifetime extended

    // DANGEROUS: lifetime extension does NOT propagate through functions
    const std::string& bad = identity(make_greeting());
    // bad is DANGLING — the temporary was destroyed after identity() returned
    // std::cout << bad << '\n';  // UNDEFINED BEHAVIOR

    // SAFE: just use a value
    std::string safe = make_greeting();  // move or copy-elision
    std::cout << safe << '\n';  // always safe
}
النقاط الرئيسية
  • Lvalue references (T&) are aliases — must be initialized, cannot be null or re-seated
  • const T&can bind to temporaries and extends their lifetime (but only when directly bound)
  • Rvalue references (T&&) bind to temporaries and enable move semantics
  • A named rvalue reference is itself an lvalue — this is critical to understand
  • Reference collapsing: any&in the chain makes the result&
  • Forwarding references (T&&with deducedT) bind to both lvalues and rvalues

اختبار — اختبر معرفتك

(15 نقطة خبرة)

1. Given`void f(std::string&& s)`, what is the value category of`s`inside`f`?

2. ما هي قاعدة الانهيار المرجعية الصحيحة؟

3. متى يعمل تمديد عمر`const T&`للمؤقتين؟