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

optional و variant و any

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

توفر لغة ++C الحديثة ثلاثة أنواع من المفردات في ترويسات <optional> و <variant> و <any> تساعدك على التعامل مع القيم التي قد تكون غائبة، أو تحمل نوعاً واحداً من عدة أنواع، أو تخزن أي نوع على الإطلاق.

std::optional<T> يمثل قيمة قد تكون موجودة أو غير موجودة - وهو مثالي للدوال التي قد تفشل دون رمي استثناء:

#include <iostream>
#include <optional>

std::optional<int> findIndex(const std::string& str, char c) {
    for (size_t i = 0; i < str.size(); ++i) {
        if (str[i] == c) return i;
    }
    return std::nullopt;  // لا توجد قيمة
}

int main() {
    auto result = findIndex("hello", 'l');
    if (result.has_value()) {
        std::cout << "Found at: " << *result << "\n";  // 2
    }
}

std::variant<Types...> هو اتحاد آمن من حيث النوع (type-safe union) يحمل بالضبط نوعاً واحداً من الأنواع المحددة في أي وقت:

#include <iostream>
#include <variant>
#include <string>

int main() {
    std::variant<int, double, std::string> data;
    
    data = 42;
    std::cout << std::get<int>(data) << "\n";
    
    data = "hello";
    if (std::holds_alternative<std::string>(data)) {
        std::cout << std::get<std::string>(data) << "\n";
    }
}

يمكن لـ std::any أن يحمل قيمة من أي نوع، يتم تحديدها في وقت التشغيل. استخدم std::any_cast لاسترداد القيمة:

#include <iostream>
#include <any>

int main() {
    std::any value = 10;
    std::cout << std::any_cast<int>(value) << "\n";
    
    value = std::string("text");
    std::cout << std::any_cast<std::string>(value) << "\n";
}

اختر std::optional للقيم التي يمكن أن تكون فارغة (nullable)، و std::variant عندما تعرف الأنواع الممكنة في وقت التجميع (compile time)، و std::any فقط عندما تحتاج حقاً إلى مرونة النوع في وقت التشغيل (runtime).

challenge icon

التحدي

سهل

لنقم ببناء نظام تكوين (configuration system) يوضح قوة أنواع المفردات (vocabulary types) في لغة C++ الحديثة. ستقوم بإنشاء مدير إعدادات مرن يتعامل مع القيم التي قد تكون غائبة، أو يمكنها الاحتفاظ بأنواع مختلفة، أو تحتاج إلى مرونة في النوع أثناء وقت التشغيل.

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

  • ConfigTypes.h: حدد أنواع قيم التكوين والدوال المساعدة.

    أنشئ دالة تسمى parseValue تأخذ سلسلة نصية وتحاول تحليلها كعدد صحيح. إذا كانت السلسلة تمثل عدداً صحيحاً صالحاً، فقم بإرجاع العدد الصحيح مغلفاً في std::optional. إذا فشل التحليل (كانت السلسلة تحتوي على أحرف غير رقمية)، فقم بإرجاع std::nullopt. للتبسيط، اعتبر السلسلة صالحة إذا كانت تحتوي فقط على أرقام (واختيارياً علامة ناقص في البداية).

    قم أيضاً بإنشاء اسم مستعار للنوع (type alias) يسمى Setting باستخدام std::variant يمكنه الاحتفاظ إما بـ int أو double أو std::string.

    أخيراً، أنشئ دالة تسمى describeSetting تأخذ Setting وترجع سلسلة نصية تصف النوع الذي تحتفظ به وقيمتها بهذا التنسيق:

    • لـ int: Integer: [value]
    • لـ double: Double: [value]
    • لـ string: String: [value]
  • DynamicStore.h: أنشئ مخزناً بسيطاً للقيم والمفاتيح (key-value store) باستخدام std::any.

    حدد فئة DynamicStore يمكنها تخزين قيم من أي نوع. يجب أن تحتوي على:

    • طريقة set تأخذ مفتاحاً نصياً وقيمة std::any، وتخزنهما داخلياً (استخدم std::map)
    • طريقة get تأخذ مفتاحاً وترجع قيمة std::any (أرجع std::any فارغاً إذا كان المفتاح غير موجود)
    • طريقة hasKey ترجع true إذا كان المفتاح موجوداً
  • main.cpp: اقرأ ثلاثة مدخلات:
    1. سلسلة نصية قد تكون رقماً (لاختبار std::optional)
    2. مؤشر نوع: int أو double أو string
    3. قيمة مقابلة لذلك النوع

    قم باستعراض جميع أنواع المفردات الثلاثة:

    أولاً، استخدم دالة parseValue مع المدخل الأول. إذا كانت تحتوي على قيمة، فاطبع Parsed: [value]. إذا كانت فارغة، فاطبع Parse failed.

    ثانياً، أنشئ متغيراً من نوع Setting بناءً على مؤشر النوع. إذا كان النوع int، فقم بتخزين القيمة كعدد صحيح. إذا كان double، فقم بتخزينها كـ double. إذا كان string، فقم بتخزينها كسلسلة نصية. ثم استدعِ describeSetting واطبع النتيجة.

    ثالثاً، أنشئ DynamicStore، وقم بتخزين السلسلة النصية "config_loaded" تحت المفتاح "status"، ثم استرجعها واطبع: Status: [value]. استخدم std::any_cast<std::string> لاستخراج القيمة.

على سبيل المثال، مع المدخلات 42 و int و 100:

Parsed: 42
Integer: 100
Status: config_loaded

مع المدخلات hello و double و 3.14:

Parse failed
Double: 3.14
Status: config_loaded

مع المدخلات -15 و string و username:

Parsed: -15
String: username
Status: config_loaded

تذكر تضمين الترويسات المناسبة: <optional> و <variant> و <any> و <string> و <map> عند الحاجة. استخدم std::holds_alternative و std::get للتعامل مع variant الخاص بك، و has_value() أو التحويل المباشر إلى boolean للتحقق من optional الخاص بك.

ورقة مرجعية

توفر لغة ++C الحديثة ثلاثة أنواع من المفردات (vocabulary types) للتعامل مع القيم المرنة:

std::optional<T>

يمثل قيمة قد تكون موجودة أو غير موجودة. قم بتضمين <optional>:

std::optional<int> findIndex(const std::string& str, char c) {
    for (size_t i = 0; i < str.size(); ++i) {
        if (str[i] == c) return i;
    }
    return std::nullopt;  // لا توجد قيمة
}

auto result = findIndex("hello", 'l');
if (result.has_value()) {
    std::cout << *result;  // إلغاء المرجعية للحصول على القيمة
}

std::variant<Types...>

اتحاد آمن من حيث النوع (type-safe union) يحتفظ بواحد فقط من الأنواع المحددة. قم بتضمين <variant>:

std::variant<int, double, std::string> data;

data = 42;
std::cout << std::get<int>(data);

data = "hello";
if (std::holds_alternative<std::string>(data)) {
    std::cout << std::get<std::string>(data);
}

std::any

يمكنه الاحتفاظ بقيمة من أي نوع، يتم تحديدها في وقت التشغيل. قم بتضمين <any>:

std::any value = 10;
std::cout << std::any_cast<int>(value);

value = std::string("text");
std::cout << std::any_cast<std::string>(value);

متى يُستخدم: std::optional للقيم التي يمكن أن تكون فارغة (nullable)، و std::variant عندما تكون الأنواع المحتملة معروفة في وقت التجميع (compile time)، و std::any للمرونة في الأنواع في وقت التشغيل (runtime).

جرّب بنفسك

#include <iostream>
#include <string>
#include <any>
#include "ConfigTypes.h"
#include "DynamicStore.h"

int main() {
    // قراءة ثلاثة مدخلات
    std::string input1;  // سلسلة نصية قد تكون رقماً (لاختبار `std::optional`‎)
    std::string typeIndicator;  // مؤشر النوع: `int` أو `double` أو `string`
    std::string value;  // القيمة المقابلة لذلك النوع
    
    std::cin >> input1;
    std::cin >> typeIndicator;
    std::cin >> value;
    
    // TODO: الجزء 1 - اختبار `std::optional` مع `parseValue`
    // استخدام دالة `parseValue` مع `input1`
    // إذا كانت تحتوي على قيمة، اطبع "Parsed: [value]"
    // إذا كانت فارغة، اطبع "Parse failed"
    
    
    // TODO: الجزء 2 - اختبار `std::variant` مع `Setting`
    // إنشاء `Setting` variant بناءً على `typeIndicator`
    // إذا كان النوع "int"، قم بتخزين القيمة كـ integer
    // إذا كان النوع "double"، قم بتخزين القيمة كـ double
    // إذا كان النوع "string"، قم بتخزين القيمة كـ string
    // ثم استدعِ `describeSetting` واطبع النتيجة
    
    
    // TODO: الجزء 3 - اختبار `std::any` مع `DynamicStore`
    // إنشاء `DynamicStore`
    // تخزين السلسلة النصية "config_loaded" تحت المفتاح "status"
    // استرجاعها وطباعة: "Status: [value]"
    // استخدام `std::any_cast<std::string>` لاستخراج القيمة
    
    
    return 0;
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming