مقدمة: حرب الرمزين
إذا كنت قد كتبت يومًا بلغة C++ وأخرجت نصًا إلى وحدة التحكم، فبالتأكيد واجهت المعضلة: استخدام std::endl أم ""؟ للوهلة الأولى، الفرق ضئيل — كلاهما ينقل السطر. لكن خلف هذا الاختيار البسيط تختبئ قصة كاملة عن الأداء، والتخزين المؤقت، والتباطؤ غير المتوقع في برنامجك.
على Stack Overflow، حصد هذا السؤال آلاف المشاهدات وأثار نقاشات حامية. اليوم سنحسم هذا الموضوع نهائيًا: أيهما أسرع، ولماذا، ومتى يكون كل خيار مبررًا. إذا كنت تكتب بلغة C++ وتريد أن يعمل كودك بأقصى كفاءة — فهذه المقالة لك.
الإجابة المختصرة (تحذير حرق)
"" دائمًا أسرع من std::endl. لكن ليس لأن std::endl بطيء بحد ذاته، بل لأنه يقوم بعمل إضافي. std::endl لا يضيف فقط حرف السطر الجديد، بل يقوم أيضًا بمسح المخزن المؤقت للإخراج (flush). وهذه عملية قد تكلفك أجزاءً ثمينة من الثانية.
دعنا نفهم ماذا يعني هذا عمليًا، ونلقي نظرة على الكود.
كيف يعمل الإخراج في C++
عندما تكتب std::cout << "Hello";، لا يتم إرسال البيانات فورًا إلى الشاشة. بل تتراكم في مخزن مؤقت — مساحة تخزين مؤقتة في الذاكرة. هذا لتحقيق الكفاءة: إرسال البيانات حرفًا حرفًا إلى الشاشة بطيء جدًا، لذا ينتظر النظام حتى تتراكم بيانات كافية، ثم يرسلها دفعة واحدة.
مسح المخزن المؤقت (flush) هو إرسال إجباري لجميع البيانات المتراكمة. وهذا بالضبط ما يفعله std::endl.
ماذا يحدث تحت الغطاء؟
// استخدام std::endlstd::cout << "السطر 1" << std::endl;// يعادل:std::cout << "السطر 1" << '' << std::flush;
// استخدام ""std::cout << "السطر 1";// يضيف فقط حرف السطر الجديد إلى المخزن المؤقتأترى الفرق؟ std::endl يقوم بعملين: إضافة '' واستدعاء std::flush. "" يقوم بعمل واحد فقط.
تجربة: قياس الأداء
دعنا نتحقق من ذلك بمثال حقيقي. سنكتب برنامجًا صغيرًا يطبع 100,000 سطر بطريقتين ويقيس الوقت.
#include <iostream>#include <chrono>
int main() { const int N = 100000; // اختبار مع std::endl auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < N; ++i) { std::cout << "رقم السطر " << i << std::endl; } auto end = std::chrono::high_resolution_clock::now(); auto duration_endl = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); std::cout << "الوقت مع endl: " << duration_endl.count() << " مللي ثانية"; // اختبار مع "" start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < N; ++i) { std::cout << "رقم السطر " << i << ""; } end = std::chrono::high_resolution_clock::now(); auto duration_n = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); std::cout << "الوقت مع \\: " << duration_n.count() << " مللي ثانية"; return 0;}النتيجة (النموذجية):
std::endl: ~350-400 مللي ثانية"": ~5-10 مللي ثانية
الفرق 30-70 مرة! وهذه ليست مزحة. كل استدعاء لـ std::endl يجبر النظام على الاتصال بنظام التشغيل لمسح المخزن المؤقت. وهذا استدعاء نظامي مكلف نسبيًا.
متى يكون flush ضروريًا؟
لكن لا تتعجل في التخلي تمامًا عن std::endl. أحيانًا يكون مسح المخزن المؤقت أمرًا ح