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).
التحدي
سهللنقم ببناء نظام تكوين (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]
- لـ int:
DynamicStore.h: أنشئ مخزناً بسيطاً للقيم والمفاتيح (key-value store) باستخدامstd::any.حدد فئة
DynamicStoreيمكنها تخزين قيم من أي نوع. يجب أن تحتوي على:- طريقة
setتأخذ مفتاحاً نصياً وقيمةstd::any، وتخزنهما داخلياً (استخدمstd::map) - طريقة
getتأخذ مفتاحاً وترجع قيمةstd::any(أرجعstd::anyفارغاً إذا كان المفتاح غير موجود) - طريقة
hasKeyترجع true إذا كان المفتاح موجوداً
- طريقة
main.cpp: اقرأ ثلاثة مدخلات:- سلسلة نصية قد تكون رقماً (لاختبار
std::optional) - مؤشر نوع:
intأوdoubleأوstring - قيمة مقابلة لذلك النوع
قم باستعراض جميع أنواع المفردات الثلاثة:
أولاً، استخدم دالة
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;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامة12ميزات C++ الحديثة
دلالات النقل و Rvaluesالتمرير المثاليتعبيرات Lambda بعمقstd::function و std::bindconstexpr و constevalالارتباطات المهيكلةoptional و variant و any