مقدمة
C++ تمنحك تحكماً كاملاً في الذاكرة، وهذا مصدر قوتها وأخطائها الشهيرة: تسريب الذاكرة، والمؤشرات المعلقة، والتحرير المزدوج. الخبر الجيد أن C++ الحديثة (منذ C++11) توفر أدوات تجعل هذه الأخطاء نادرة إن استخدمتها بشكل صحيح.
أهداف التعلم
- فهم عمر الكائنات على المكدس والكومة.
- تطبيق مبدأ RAII.
- استخدام
std::unique_ptrوstd::shared_ptrبدلnew/delete. - الاعتماد على الحاويات القياسية مثل
std::vectorوstd::string.
1. المكدس: العمر مرتبط بالنطاق
#include <string>
void greet() {
std::string name = "سارة"; // يُنشأ هنا
// ...
} // يُدمَّر تلقائياً عند الخروج من النطاق
الكائنات على المكدس تُحرر تلقائياً بترتيب عكسي لإنشائها. سريعة وآمنة، لكن حجمها محدود وعمرها لا يتجاوز النطاق.
2. الكومة والمشكلة القديمة
int* scores = new int[100];
// ... إن حدث استثناء أو return مبكر هنا، لن يصل البرنامج إلى السطر التالي
delete[] scores; // نسيانه = تسريب، تكراره = سلوك غير معرّف
الإدارة اليدوية هشة: كل مسار خروج من الدالة يجب أن يتذكر التحرير.
3. RAII: اربط المورد بعمر كائن
RAII (Resource Acquisition Is Initialization) يعني: الكائن يحجز المورد في المُنشئ ويحرره في المُدمِّر. بما أن المُدمِّر يُستدعى تلقائياً عند الخروج من النطاق — حتى مع الاستثناءات — لا ينسى أحد التحرير.
std::vector وstd::string وstd::fstream كلها تطبق RAII:
#include <vector>
#include <numeric>
double average(int n) {
std::vector<int> scores(n, 0); // ذاكرة على الكومة، يديرها vector
// ... ملء القيم
if (scores.empty()) return 0.0; // خروج مبكر آمن: vector يحرر ذاكرته
return std::accumulate(scores.begin(), scores.end(), 0.0) / scores.size();
}
4. المؤشرات الذكية
#include <memory>
struct Session { std::string user; };
std::unique_ptr<Session> open(std::string user) {
return std::make_unique<Session>(Session{std::move(user)});
}
int main() {
auto s = open("ahmed"); // مالك وحيد
auto t = std::move(s); // نقل الملكية؛ s أصبح فارغاً
// لا delete: تحرَّر عند انتهاء نطاق t
}
std::unique_ptr: مالك وحيد، بلا تكلفة إضافية تقريباً. الاختيار الافتراضي.std::shared_ptr: ملكية مشتركة بعداد مراجع. استخدمه فقط عندما تكون الملكية مشتركة فعلاً.std::weak_ptr: مرجع لا يملك، لكسر دوائرshared_ptr.
واستخدم دائماً std::make_unique وstd::make_shared بدل new.
5. المؤشرات والمراجع الخام
المؤشر الخام (T*) أو المرجع (T&) مناسبان للاستخدام دون ملكية: دالة تقرأ كائناً لا تملكه. القاعدة: الملكية بالمؤشر الذكي أو القيمة، والاستعارة بالمرجع.
void print(const Session& s) { std::cout << s.user << '\n'; } // لا يملك
احذر المرجع المعلق: لا تعِد مرجعاً أو مؤشراً إلى متغير محلي.
const std::string& bad() { std::string s = "x"; return s; } // معلّق! s دُمّر
6. أدوات تكشف الأخطاء
g++ -std=c++20 -g -fsanitize=address,undefined main.cpp -o main && ./main
AddressSanitizer يكشف التسريب، والكتابة خارج الحدود، واستخدام الذاكرة بعد تحريرها، مع رقم السطر.
تمرين عملي
- اكتب صنفاً
FileReaderيفتح ملفاً في المُنشئ ويغلقه في المُدمِّر، واطبع رسالة في كل منهما، ثم ارمِ استثناء داخل النطاق ولاحظ أن الإغلاق حدث. - حوّل كوداً قديماً يستخدم
new[]/delete[]إلىstd::vector. - شغّل برنامجاً فيه تسريب متعمد مع
-fsanitize=addressواقرأ التقرير.
أخطاء شائعة
newوdeleteيدوياً في كود حديث.shared_ptrفي كل مكان «احتياطاً» (تكلفة وغموض في الملكية).- إعادة مراجع لمتغيرات محلية.
- الخلط بين
deleteوdelete[].
الخلاصة
اجعل كل مورد مملوكاً لكائن يحرره تلقائياً (RAII). استخدم الحاويات القياسية وunique_ptr افتراضياً، وshared_ptr عند الحاجة الحقيقية فقط، وشغّل المُعقِّمات (Sanitizers) أثناء التطوير.
مصادر مقترحة
- C++ Core Guidelines (قسم Resource management).
- موقع cppreference.com.
- كتاب A Tour of C++ لـ Bjarne Stroustrup.