C++: دلالات النقل بعمق

آخر تحديث: 2026-08-26

في الدرس 44، أنهينا تعلم مزامنة تعدد المسارات.

الآن، سنتعمق في واحدة من أهم ميزات C++11 - دلالات النقل.

فهم دلالات النقل هو مفتاح فهم تحسين أداء C++ الحديثة حقاً.


1. نظرة عامة على دلالات النقل

(1) 1.1 لماذا نحتاج دلالات النقل؟

المشكلة: في C++98/03، نسخ الكائنات المؤقتة (القيم اليمنى) مضيعة.

مثال: نسخ كائنات مؤقتة (الصعوبة ⭐)

▶ مثال 2: استخدام حاوية STL (الصعوبة ⭐)

TEXT 📖 للعرض فقط
std::vector<int> createVector() {
 std::vector<int> v = {1, 2, 3, 4, 5};
 return v; // إرجاع كائن مؤقت
}

std::vector<int> dest = createVector(); // نسخ الكائن المؤقت، مضيعة!

الخرج:

TEXT 📖 للعرض فقط
Copy count: 0

حل دلالات النقل: "سرقة" موارد الكائن المؤقت بدلاً من نسخها.


(2) 1.2 القيمة اليسارية مقابل القيمة اليمنى

الفئة الوصف مثال
قيمة يسارية لها اسم، يمكن أخذ عنوانها x في int x = 10;
قيمة يمنى كائن مؤقت، على وشك التدمير 10، x + 1، قيم إرجاع الدوال

تشبيه من الحياة الواقعية:



2. مراجع القيم اليمنى

(1) 2.1 الصياغة الأساسية

تستخدم مراجع القيم اليمنى && ولا يمكنها الارتباط إلا بالقيم اليمنى.

مثال: مراجع القيم اليمنى (الصعوبة ⭐)

CPP
#include <iostream>

void process(int& x) {
 std::cout << "Processing lvalue: " << x << std::endl;
}

void process(int&& x) {
 std::cout << "Processing rvalue: " << x << std::endl;
}

int main() {
 int a = 10;
 process(a); // يستدعي process(int&)
 process(20); // يستدعي process(int&&)
 
 return 0;
}

(2) 2.2 std::move

يُستخدم std::move لتحويل قيمة يسارية إلى مرجع قيمة يمنى، مشيراً إلى "لم أعد بحاجة إلى هذا الكائن، يمكنك سرقة موارده."

مثال: استخدام std::move لنقل الموارد (الصعوبة ⭐⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <vector>
#include <utility>

int main() {
 std::vector<int> v1 = {1, 2, 3};
 std::vector<int> v2 = std::move(v1); // بناء النقل: موارد v1 "سُرقت" بواسطة v2
 
 std::cout << "v2 size: " << v2.size() << std::endl; // 3
 std::cout << "v1 size: " << v1.size() << std::endl; // 0 (v1 فارغة بعد النقل)
 
 return 0;
}

💡 نصيحة:



3. باني النقل وعامل تعيين النقل

(1) 3.1 لماذا نحتاج عمليات نقل مخصصة؟

المشكلة: عمليات النقل التي يُنشئها المترجم قد لا تكون فعالة (مثلاً للفئات ذات النسخ العميقة).

الحل: عرّف باني نقل مخصص وعامل تعيين نقل مخصص.


(2) 3.2 مثال: تطبيق عمليات النقل (الصعوبة ⭐⭐⭐)

CPP
#include <iostream>
#include <cstring>

class String {
private:
 char* data;
 size_t length;

public:
 // باني
 String(const char* str) {
 length = strlen(str);
 data = new char[length + 1];
 strcpy(data, str);
 std::cout << "Construct: " << data << std::endl;
 }
 
 // باني نسخ (نسخة عميقة)
 String(const String& other) {
 length = other.length;
 data = new char[length + 1];
 strcpy(data, other.data);
 std::cout << "Copy construction: " << data << std::endl;
 }
 
 // باني نقل (سرقة الموارد)
 String(String&& other) noexcept {
 data = other.data; // سرقة المؤشر
 length = other.length;
 other.data = nullptr; // تعيين إلى قيمة فارغة، منع التحرير المزدوج
 other.length = 0;
 std::cout << "Move construction" << std::endl;
 }
 
 // مدمر
 ~String() {
 delete data;
 }
};

int main() {
 String s1("Hello");
 String s2 = std::move(s1); // يستدعي باني النقل
 
 return 0;
}

الخرج:

TEXT 📖 للعرض فقط
Construct: Hello
Move construction

نتيجة التنفيذ:

TEXT 📖 للعرض فقط
Construct: Hello
Move construction


4. قواعد دلالات النقل

(1) 4.1 الخمسة الكبار

إذا كانت الفئة تحتاج إلى مدمر مخصص، أو باني نسخ، أو عامل تعيين نسخ، فغالباً تحتاج أيضاً إلى باني نقل وعامل تعيين نقل مخصصين.

الدالة الوصف
المدمر تحرير الموارد
باني النسخ نسخة عميقة
عامل تعيين النسخ تعيين نسخة عميقة
باني النقل سرقة الموارد
عامل تعيين النقل تعيين سرقة الموارد

(2) 4.2 قاعدة الصفر

أفضل ممارسة: إذا استخدمت مؤشرات ذكية لإدارة الموارد، لا تحتاج إلى تعريف أي من الخمسة الكبار (سينشئ المترجم تلقائياً إصدارات صحيحة).

TEXT 📖 للعرض فقط
class Person {
 std::string name; // باستخدام string، يدعم النقل تلقائياً
 std::vector<int> scores; // باستخدام vector، يدعم النقل تلقائياً
 // لا حاجة لتعريف الخمسة الكبار!
}


5. التوجيه المثالي

(1) 5.1 ما هو التوجيه المثالي؟

التوجيه المثالي يعني تمرير المعاملات إلى دوال أخرى دون تغيير طبيعتها كقيمة يسارية/يمنى.

مثال: استخدام std::forward للتوجيه المثالي (الصعوبة ⭐⭐⭐)

CPP
#include <iostream>
#include <utility>

void process(int& x) {
 std::cout << "Processing lvalue" << std::endl;
}

void process(int&& x) {
 std::cout << "Processing rvalue" << std::endl;
}

template<typename T>
void wrapper(T&& arg) {
 process(std::forward<T>(arg)); // توجيه مثالي
}

int main() {
 int x = 10;
 wrapper(x); // يُوجَّه كقيمة يسارية
 wrapper(20); // يُوجَّه كقيمة يمنى
 
 return 0;
}


6. مزايا أداء دلالات النقل

▶ مثال 1: مقارنة بين النسخ والنقل (الصعوبة ⭐⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <vector>
#include <chrono>

int main() {
 std::vector<std::vector<int>> v;
 
 // اختبار النسخ
 auto start = std::chrono::high_resolution_clock::now();
 for (int i = 0; i < 10000; i++) {
 std::vector<int> temp(1000, 1);
 v.push_back(temp); // نسخ
 }
 auto end = std::chrono::high_resolution_clock::now();
 auto copy_time = std::chrono::duration<double>(end - start).count();
 
 v.clear();
 
 // اختبار النقل
 start = std::chrono::high_resolution_clock::now();
 for (int i = 0; i < 10000; i++) {
 std::vector<int> temp(1000, 1);
 v.push_back(std::move(temp)); // نقل
 }
 end = std::chrono::high_resolution_clock::now();
 auto move_time = std::chrono::duration<double>(end - start).count();
 
 std::cout << "Copy time: " << copy_time << " seconds" << std::endl;
 std::cout << "Move time: " << move_time << " seconds" << std::endl;
 
 return 0;
}

الخرج:

TEXT 📖 للعرض فقط
Copy time: 0.5 seconds
Move time: 0.01 seconds

▶ مثال 3: std::move ينقل ملكية الموارد (الصعوبة ⭐)

CPP
#include <iostream>
#include <vector>
#include <string>

int main() {
    std::string original = "Hello, C++!";
    std::string moved = std::move(original);

    std::cout << "moved: " << moved << std::endl;
    std::cout << "original after move: \"" << original << "\"" << std::endl;

    return 0;
}
▶ جرّب الكود

الخرج:

TEXT 📖 للعرض فقط
moved: Hello, C++!
original after move: ""

❓ أسئلة شائعة

س هل std::move ينفذ النقل دائماً؟
ج ليس بالضرورة. إذا لم يكن هناك باني نقل، سيُستدعى باني النسخ بدلاً منه (تراجع).

س: في أي حالة يكون الكائن بعد النقل؟ ج: الكائن بعد النقل يكون في حالة صالحة لكن غير محددة (عادة فارغة). يمكنك تعيين قيمة جديدة له، لكن لا يمكنك افتراض قيمته.


س متى أستخدم دلالات النقل؟
ج - الكائنات المؤقتة (قيم إرجاع الدوال) - الكائنات التي لم تعد بحاجة إليها (std::move) - إدراج عناصر الحاوية (emplace_back)

📖 ملخص

المفهوم النقطة الأساسية
مرجع القيمة اليمنى &&، يمكنه الارتباط فقط بالقيم اليمنى
std::move يحوّل قيمة يسارية إلى مرجع قيمة يمنى
باني النقل يسرق الموارد، لا نسخ
عامل تعيين النقل يسرق الموارد للتعيين
التوجيه المثالي std::forward، يحافظ على فئة القيمة

📝 تمارين

  1. أساسي (الصعوبة ⭐): أنشئ vector<string>، أضف عدة نصوص باستخدام push_back، ولاحظ الخرج لتأكيد حدوث النسخ. ثم قارن مع emplace_back.

  2. متوسط (الصعوبة ⭐⭐): طبّق فئة MyString (بكل من باني النسخ وباني النقل)، فعّل دلالات النقل عبر std::move في main، ولاحظ أي باني يُستدعى.

  3. متقدم (الصعوبة ⭐⭐⭐): طبّق نوعاً يدعم النقل فقط (مثل unique_ptr)، عطّل باني النسخ وعامل تعيين النسخ، فعّل باني النقل وعامل تعيين النقل. اكتب كود اختبار للتحقق من أنه لا يمكن نسخه لكن يمكن نقله.



الدرس التالي: المؤشرات الذكية المتقدمة (#46)

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%