تحميل عامل التعيين
جزء من قسم 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;. وهذا يطابق سلوك الأنواع المضمنة.
بالنسبة للأصناف التي تدير الموارد، غالبًا ما ستقوم بتنفيذ كل من عاملي تعيين النسخ (copy) والنقل (move):
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data; // تنظيف المورد الحالي
data = other.data; // الاستيلاء على المورد
size = other.size;
other.data = nullptr; // ترك المصدر في حالة صالحة
other.size = 0;
}
return *this;
}ينقل عامل تعيين النقل الملكية بدلاً من النسخ، مما يجعل عمليات مثل 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) في ملف الرأس الخاص بك.
ورقة مرجعية
يقوم عامل تعيين النسخ (copy assignment operator) (=) بتعيين كائن موجود إلى كائن آخر. يجب أن يتعامل مع تنظيف الموارد الموجودة قبل نسخ البيانات الجديدة.
هيكل عامل تعيين النسخ
ClassName& operator=(const ClassName& other) {
if (this != &other) { // 1. Self-assignment check
// 2. Clean up existing resources
// 3. Copy data from other
}
return *this; // 4. Return *this
}مثال مع الذاكرة الديناميكية
class Buffer {
int* data;
size_t size;
public:
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data; // Clean up existing resource
size = other.size;
data = new int[size];
for (size_t i = 0; i < size; ++i)
data[i] = other.data[i];
}
return *this;
}
};يمنع التحقق من التعيين الذاتي (this != &other) حذف البيانات قبل نسخها في حالات مثل b = b;.
إرجاع *this بالمرجع (by reference) يتيح التسلسل: a = b = c;.
عامل تعيين النقل (Move Assignment Operator)
يقوم عامل تعيين النقل بنقل الملكية بدلاً من النسخ، مما يحسن الكفاءة:
ClassName& operator=(ClassName&& other) noexcept {
if (this != &other) {
// Clean up current resource
// Steal the resource from other
// Leave other in valid state (nullptr, 0, etc.)
}
return *this;
}مثال على تعيين النقل
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data; // Steal the resource
size = other.size;
other.data = nullptr; // Leave source valid
other.size = 0;
}
return *this;
}قم بتمييز تعيين النقل بـ noexcept للحصول على أداء مثالي مع الحاويات القياسية (standard containers).
جرّب بنفسك
#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: طباعة str1 و str2 بعد تخصيص النسخ
// التنسيق:
// After copy (str1 = str2):
// str1: <value>
// str2: <value>
// TODO: استخدام تخصيص النقل: str2 = std::move(str3);
// TODO: طباعة str2 بعد النقل، وطول str3
// التنسيق:
// 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مراجعة - الحاويات العامة