Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

std::function و std::bind

جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 86 من 104.

بينما تُعد تعبيرات لامبدا (lambdas) قوية، قد تحتاج أحياناً إلى تخزين كائنات قابلة للاستدعاء (callable objects) بأنواع مختلفة بطريقة موحدة، أو تكييف الدوال الموجودة لتتوافق مع توقيع (signature) مطلوب. توفر كل من std::function و std::bind من الترويسة <functional> حلولاً لهذه المشكلات.

std::function هو غلاف ممحو النوع (type-erased wrapper) يمكنه الاحتفاظ بأي كائن قابل للاستدعاء يطابق توقيعاً معيناً - الدوال، أو تعبيرات لامدا، أو كائنات الدوال:

#include <iostream>
#include <functional>

int add(int a, int b) { return a + b; }

int main() {
    std::function<int(int, int)> operation;
    
    operation = add;                              // دالة عادية
    std::cout << operation(3, 4) << "\n";        // 7
    
    operation = [](int a, int b) { return a * b; }; // لامدا
    std::cout << operation(3, 4) << "\n";        // 12
}

يقوم std::bind بإنشاء كائن قابل للاستدعاء (callable) جديد عن طريق تثبيت بعض وسائط دالة موجودة. استخدم std::placeholders::_1، و _2، إلخ. لتمييز الوسائط التي تظل متغيرة:

#include <iostream>
#include <functional>

void greet(const std::string& greeting, const std::string& name) {
    std::cout << greeting << ", " << name << "!\n";
}

int main() {
    using namespace std::placeholders;
    
    auto sayHello = std::bind(greet, "Hello", _1);
    sayHello("Alice");  // Hello, Alice!
    
    auto swapped = std::bind(greet, _2, _1);
    swapped("Bob", "Hi");  // Hi, Bob!
}

تعتبر هذه الأدوات مفيدة بشكل خاص في البرمجة كائنية التوجه (OOP) لتخزين الاستدعاءات الراجعة (callbacks) كأعضاء في الفئة أو تنفيذ نمط الاستراتيجية (Strategy pattern). ومع ذلك، غالبًا ما تفضل لغة C++ الحديثة تعبيرات لامبدا (lambdas) على std::bind لتحسين قابلية القراءة والأداء - استخدم std::bind بشكل أساسي عندما تحتاج إلى إعادة ترتيب الوسائط (arguments) أو العمل مع الأكواد القديمة (legacy code).

challenge icon

التحدي

سهل

لنقم ببناء آلة حاسبة قابلة للتكوين توضح قوة std::function و std::bind. ستقوم بإنشاء نظام يمكن فيه تخزين العمليات الحسابية وتبديلها وتخصيصها في وقت التشغيل — مما يوضح كيف تمكن هذه الأدوات من إدارة عمليات الاستدعاء (callbacks) بمرونة.

ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:

  • MathOperations.h: قم بتعريف مجموعة من الدوال الحسابية المستقلة التي ستكون بمثابة مكتبة العمليات الخاصة بك.

    أنشئ هذه الدوال:

    • add(int a, int b) — تعيد المجموع
    • subtract(int a, int b) — تعيد الفرق (a - b)
    • multiply(int a, int b) — تعيد ناتج الضرب
    • power(int base, int exponent, int multiplier) — تعيد multiplier * (base ^ exponent). استخدم حلقة تكرار بسيطة لحساب القوة (افترض أن الأسس غير سالبة).
  • Calculator.h: قم بتعريف فئة Calculator تستخدم std::function لتخزين العمليات وتنفيذها ديناميكيًا.

    يجب أن تحتوي الآلة الحاسبة الخاصة بك على:

    • عضو خاص (private member) من نوع std::function<int(int, int)> لتخزين العملية الثنائية الحالية
    • setOperation(std::function<int(int, int)> op) — لتعيين العملية الحالية
    • calculate(int a, int b) — لتنفيذ العملية المخزنة وإرجاع النتيجة

    قم بتضمين الترويسات اللازمة (<functional>) وتأكد من تضمين ترويسة MathOperations الخاصة بك.

  • main.cpp: اقرأ ثلاثة مدخلات:
    1. اسم العملية: add، subtract، multiply، أو square
    2. الرقم الأول (عدد صحيح)
    3. الرقم الثاني (عدد صحيح)

    أنشئ كائنًا من Calculator وقم بتكوينه بناءً على اسم العملية:

    • بالنسبة لـ add و subtract و multiply: قم بتعيين الدالة المقابلة مباشرة إلى الآلة الحاسبة
    • بالنسبة لـ square: استخدم std::bind لإنشاء نسخة متخصصة من power تستخدم دائمًا الأس 2 والمضاعف 1. يجب أن تقبل الدالة المرتبطة وسيطين حيث يتم استخدام الأول فقط كأساس (يمكن تجاهل الوسيط الثاني باستخدام placeholder).

    بعد تعيين العملية، استدعِ calculate() مع الرقمين وقم بطباعة:

    Result: [value]

    ثم وضح عملية تبديل العمليات عن طريق تعيين lambda تعيد a + b + 100 إلى الآلة الحاسبة، وتشغيلها بنفس المدخلات، وطباعة:

    With bonus: [value]

على سبيل المثال، مع المدخلات add و 10 و 5:

Result: 15
With bonus: 115

مع المدخلات multiply و 7 و 3:

Result: 21
With bonus: 110

مع المدخلات square و 4 و 0:

Result: 16
With bonus: 104

يوضح هذا التحدي كيف توفر std::function طريقة موحدة لتخزين أنواع مختلفة من الكائنات القابلة للاستدعاء (الدوال العادية، الدوال المرتبطة، و lambdas)، بينما تتيح لك std::bind تكييف الدوال الموجودة عن طريق تثبيت بعض وسائطها.

جرّب بنفسك

#include <iostream>
#include <string>
#include <functional>
#include "Calculator.h"

using namespace std;

int main() {
    // قراءة المدخلات
    string operation;
    int num1, num2;
    cin >> operation >> num1 >> num2;
    
    // إنشاء كائن من فئة Calculator
    Calculator calc;
    
    // TODO: تهيئة الآلة الحاسبة بناءً على اسم العملية
    // لعمليات "add" و "subtract" و "multiply": قم بتعيين الدالة المقابلة مباشرة
    // لعملية "square": استخدم std::bind لإنشاء نسخة مخصصة من دالة power
    //   التي تستخدم دائماً الأس 2 والمضاعف 1
    //   تلميح: استخدم std::placeholders::_1 للمعامل الأساسي
    
    if (operation == "add") {
        // الكود الخاص بك هنا
    } else if (operation == "subtract") {
        // الكود الخاص بك هنا
    } else if (operation == "multiply") {
        // الكود الخاص بك هنا
    } else if (operation == "square") {
        // الكود الخاص بك هنا - استخدم std::bind مع دالة power
    }
    
    // TODO: استدعاء calculate() وطباعة النتيجة
    // التنسيق: "Result: [value]"
    
    // TODO: توضيح تبديل العمليات
    // قم بتعيين تعبير lambda يعيد a + b + 100 إلى الآلة الحاسبة
    // استدعِ calculate() مرة أخرى واطبع النتيجة
    // التنسيق: "With bonus: [value]"
    
    return 0;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming