تحميل عامل التعيين
جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 44 من 104.
يتم استدعاء عامل تعيين النسخ (=) عندما تقوم بتعيين كائن موجود إلى كائن آخر. على عكس منشئ النسخ (الذي ينشئ كائنًا جديدًا)، يعمل عامل التعيين مع كائن موجود بالفعل وقد يحتوي على موارد تحتاج إلى التنظيف أولاً.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
~Buffer() { delete[] data; }
Buffer& operator=(const Buffer& other) {
if (this != &other) { // 1. التحقق من التعيين الذاتي
delete[] data; // 2. تنظيف المورد الموجود
size = other.size; // 3. نسخ البيانات
data = new int[size];
for (size_t i = 0; i < size; ++i)
data[i] = other.data[i];
}
return *this; // 4. إرجاع *this
}
};يعد التحقق من التعيين الذاتي (this != &other) أمراً بالغ الأهمية. فبدونه، سيؤدي b = b; إلى حذف البيانات قبل محاولة نسخها، مما يتسبب في سلوك غير محدد.
يعيد العامل *this بالمرجع لتمكين التسلسل مثل a = b = c;. وهذا يطابق سلوك الأنواع المدمجة.
بالنسبة للأصناف (classes) التي تدير الموارد، غالبًا ما ستقوم بتنفيذ كل من عاملي تعيين النسخ والنقل (move assignment operators):
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data; // تنظيف المورد الحالي
data = other.data; // سرقة المورد
size = other.size;
other.data = nullptr; // ترك المصدر في حالة صالحة
other.size = 0;
}
return *this;
}ينقل عامل تعيين النقل (move assignment operator) الملكية بدلاً من النسخ، مما يجعل عمليات مثل vec[0] = createBuffer(); أكثر كفاءة بكثير.
التحدي
سهللنقم ببناء فئة DynamicString تدير مصفوفة أحرف مخصصة ديناميكيًا وتتعامل بشكل صحيح مع عملية التعيين (assignment) بين الكائنات الموجودة. هذا سيناريو عملي حيث تحتاج إلى نقل بيانات السلسلة النصية بأمان من كائن إلى آخر دون حدوث تسرب للذاكرة (memory leaks) أو مؤشرات معلقة (dangling pointers).
ستقوم بإنشاء ملفين لتنظيم الكود الخاص بك:
DynamicString.h: قم بتعريف فئةDynamicStringالتي تخزن سلسلة نصية بنمط C باستخدام الذاكرة الديناميكية. يجب أن تدير فئتك ما يلي:- مؤشر
char*خاص لبيانات السلسلة النصية - متغير
size_tخاص للطول - منشئ (constructor) يأخذ
const char*ويقوم بإنشاء نسخة عميقة (deep copy) - مدمر (destructor) يقوم بتحرير الذاكرة المخصصة بشكل صحيح
- عامل تعيين النسخ (copy assignment operator) يقوم بنسخ عميق مع الحماية من التعيين الذاتي (self-assignment protection)
- عامل تعيين النقل (move assignment operator) ينقل الملكية بكفاءة
- دالة
c_str()تعيد السلسلة النصية الداخلية (const) - دالة
length()تعيد طول السلسلة النصية (const)
يجب أن يتبع عامل تعيين النسخ الخطوات الأربع الأساسية: التحقق من التعيين الذاتي، تنظيف الموارد الموجودة، نسخ البيانات الجديدة، وإرجاع
*this. يجب أن يقوم عامل تعيين النقل بـ "سرقة" مؤشر المصدر وترك المصدر في حالة فارغة صالحة (nullptr مع طول 0).- مؤشر
main.cpp: اقرأ ثلاث سلاسل نصية من المدخلات (كل واحدة في سطر منفصل). قم بإنشاء ثلاثة كائناتDynamicString:str1مع المدخل الأول،str2مع المدخل الثاني، وstr3مع المدخل الثالث.قم باستعراض عوامل التعيين الخاصة بك:
- اطبع الحالة الأولية لجميع السلاسل الثلاث
- استخدم تعيين النسخ:
str1 = str2; - اطبع str1 و str2 بعد تعيين النسخ (يجب أن يكون لكليهما نفس المحتوى)
- استخدم تعيين النقل:
str2 = std::move(str3); - اطبع str2 بعد تعيين النقل، وطول str3 (يجب أن يكون 0 بعد النقل منه)
تنسيق المخرجات:
Initial: str1: <value> str2: <value> str3: <value> After copy (str1 = str2): str1: <value> str2: <value> After move (str2 = std::move(str3)): str2: <value> str3 length: 0
استخدم <cstring> من أجل strlen و strcpy. قم بتمييز عامل تعيين النقل بـ noexcept. قم بتضمين <utility> في main.cpp من أجل std::move. لا تنسَ حراس الرأس (header guards) في ملف الرأس الخاص بك.
جرّب بنفسك
#include <iostream>
#include <string>
#include <utility>
#include "DynamicString.h"
using namespace std;
int main() {
// قراءة ثلاث سلاسل نصية من المدخلات
string input1, input2, input3;
getline(cin, input1);
getline(cin, input2);
getline(cin, input3);
// DynamicString إنشاء ثلاثة كائنات من نوع
DynamicString str1(input1.c_str());
DynamicString str2(input2.c_str());
DynamicString str3(input3.c_str());
// TODO: طباعة الحالة الأولية لجميع السلاسل الثلاث
// التنسيق:
// Initial:
// str1: <value>
// str2: <value>
// str3: <value>
// TODO: str1 = str2; :استخدام إسناد النسخ
// TODO: بعد إسناد النسخ str2 و str1 طباعة
// التنسيق:
// After copy (str1 = str2):
// str1: <value>
// str2: <value>
// TODO: str2 = std::move(str3); :استخدام إسناد النقل
// TODO: str3 وطول str2 طباعة بعد النقل
// التنسيق:
// After move (str2 = std::move(str3)):
// str2: <value>
// str3 length: 0
return 0;
}يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامة