تعبيرات لامدا في C++: متى تعمل حقًا على تسريع الكود؟

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

تعبيرات لامدا في C++: متى تعمل حقًا على تسريع الكود؟

\n\n

مرحبًا، زميلي! إذا كنت قد ألقيت نظرة على Stack Overflow من قبل، فمن المحتمل أنك رأيت سؤالًا جمع آلاف المشاهدات: "ما هو تعبير لامدا، ومتى يجب أن أستخدم واحدًا؟". وهذا ليس من قبيل الصدفة. لامدا في C++ هي موضوع يبسط الحياة وفي نفس الوقت يثير الكثير من الجدل حول التحسين والأداء.

\n\n

اليوم لن نقدم مجرد تعريف جاف. سنفهم كيف تعمل تعبيرات لامدا تحت الغطاء، ومتى يجعل استخدامها الكود أسرع، ومتى - على العكس - يخلق مشاكل خفية. هيا بنا!

\n\n

ما هو تعبير لامدا؟ نظرة من الداخل

\n\n

رسميًا، تعبير لامدا هو دالة مجهولة يمكن تعريفها مباشرة في مكان الاستدعاء. ولكن إذا تعمقنا أكثر، يحول المترجم كل لامدا إلى كائن وظيفي فريد (functor). إليك مثال:

\n\n
// لامدا\nauto add = [](int a, int b) { return a + b; };\n\n// ما يتحول إليه (مبسط)\nclass __lambda_1 {\npublic:\n    auto operator()(int a, int b) const { return a + b; }\n};\n__lambda_1 add;
\n\n

النقطة الأساسية: كل لامدا لها نوع فريد خاص بها. هذا يعني أن المترجم يمكنه تضمين الاستدعاءات بقوة - على عكس المؤشرات للدوال أو std::function.

\n\n

الأداء: أين تتفوق لامدا؟

\n\n

لنذهب مباشرة إلى صلب الموضوع. إليك ثلاثة سيناريوهات حيث تعطي لامدا زيادة ملحوظة في الأداء:

\n\n

1. التضمين وغياب الاستدعاءات غير المباشرة

\n\n

عندما تمرر لامدا إلى دالة قالب (مثل std::sort)، يعرف المترجم النوع الدقيق. هذا يسمح له بتضمين كود لامدا مباشرة في الدالة المستدعية. قارن:

\n\n
// الخيار 1: مؤشر لدالة (أبطأ)\nbool compare(int a, int b) { return a < b; }\nstd::sort(vec.begin(), vec.end(), compare);\n\n// الخيار 2: لامدا (أسرع)\nstd::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; });
\n\n

في الحالة الأولى - استدعاء غير مباشر عبر مؤشر. في الثانية - تضمين مباشر. على كميات كبيرة من البيانات (ملايين العناصر) يمكن أن يصل الفرق إلى 20-30% في الوقت.

\n\n

2. التقاط المتغيرات بدون نسخ غير ضروري

\n\n

تسمح لامدا بالتحكم المرن في الالتقاط. الاختيار الصحيح بين [=] و [&] والمتغيرات المحددة يؤثر مباشرة على الأداء:

\n\n
std::vector<std::string> data;\nstd::string prefix = "prefix_";\n\n// سيء: نسخ سلسلة ثقيلة\nstd::for_each(data.begin(), data.end(), \n    [=](std::string& s) { s = prefix + s; });\n\n// جيد: التقاط بالمرجع\nstd::for_each(data.begin(), data.end(), \n    [&](std::string& s) { s = prefix + s; });
\n\n

الالتقاط بالقيمة ([=]) ينشئ نسخة من prefix داخل الكائن الوظيفي. بالنسبة لـ std::string قد يكون هذا مكلفًا. الالتقاط بالمرجع ([&]) خفيف الوزن.

\n\n

3. لامدا في خوارزميات STL

\n\n

تستخدم خوارزميات STL الحديثة (C++17/20) لامدا بنشاط لـ الحسابات المتوازية. على سبيل المثال، std::transform مع سياسة التنفيذ std::par:

\n\n
std::vector<int> input(1000000);\nstd::vector<int> output(input.size());\n\n// معالجة متوازية\nstd::transform(std::execution::par, \n    input.begin(), input.end(), output.begin(),\n    [](int x) { return x * x + 2 * x + 1; });
\n\n

لامدا هنا هي المرشح المثالي للتوازي، حيث لا تحتوي على آثار جانبية (دالة نقية). يمكن للمترجم والمكتبة تشغيلها بأمان على عدة خيوط.

\n\n

متى يمكن أن تضر لامدا بالأداء؟

\n\n

سيكون من غير العدل الحديث فقط عن الإيجابيات. هناك حالات حيث لامدا ليست الخيار الأفضل:

\n\n

1. الالتقاط الثقيل بالقيمة

\n\n

إذا قمت بالتقاط

المدونات

توصيات الكتب