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) يمكنه الاحتفاظ بأي كائن قابل للاستدعاء يطابق توقيعاً محدداً - الدوال، أو تعبيرات لامبدا (lambdas)، أو كائنات الدوال:

#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 بشكل أساسي عندما تحتاج إلى إعادة ترتيب الوسائط أو العمل مع الكود القديم (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 لتخزين وتنفيذ العمليات ديناميكيًا.

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

    • عضو خاص 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 تكييف الدوال الموجودة عن طريق تثبيت بعض وسائطها.

ورقة مرجعية

يوفر الترويسة <functional> كلاً من std::function و std::bind للتعامل مع الكائنات القابلة للاستدعاء (callable objects) بطريقة مرنة.

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

#include <functional>

std::function<int(int, int)> operation;

operation = add;                              // دالة عادية
operation = [](int a, int b) { return a * b; }; // Lambda

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

#include <functional>

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

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!

هذه الأدوات مفيدة لتخزين الاستدعاءات الراجعة (callbacks) كأعضاء في الفئات (class members) أو لتنفيذ أنظمة استدعاء راجع مرنة. غالباً ما تفضل لغة C++ الحديثة استخدام 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