Kodax

أدوات C++ الحديثة: optional و variant و string_view — C++ المتقدم

أدوات C++ الحديثة

أضافت C++17 أنواعاً صغيرة تحل مشاكل يومية كانت تُحل بحيل مثل "أعد -1 عند الفشل" أو union خطيرة. هذا الدرس يعرض أهمها.

std::optional: قيمة قد لا توجد

كيف تعيد دالة البحث "لم أجد"؟ إرجاع -1 يخلط القيمة بالخطأ (ماذا لو كانت -1 قيمة صالحة؟)، ورمي استثناء مبالغة لحالة متوقعة. optional<T> إما يحمل T أو لا شيء:

#include <iostream>
#include <optional>
#include <string>
#include <vector>
using namespace std;

optional<int> findIndex(const vector<string>& v, const string& target) {
    for (size_t i = 0; i < v.size(); i++)
        if (v[i] == target) return static_cast<int>(i);
    return nullopt;                         // "لا شيء"
}

int main() {
    vector<string> names = {"سارة", "عمر"};
    if (auto idx = findIndex(names, "عمر"))  // optional يُقيَّم كـ bool
        cout << "وُجد في " << *idx << '\n';
    cout << findIndex(names, "زيد").value_or(-1) << '\n';   // -1
}

| العملية | المعنى |

|---|---|

| if (opt) / opt.has_value() | هل توجد قيمة؟ |

| *opt | القيمة (سلوك غير معرّف إن كانت فارغة) |

| opt.value() | القيمة أو يرمي bad_optional_access |

| opt.value_or(x) | القيمة أو x |

structured bindings: تفكيك بأسماء

#include <map>
#include <string>
#include <tuple>
using namespace std;

tuple<int, int, double> stats() { return {3, 97, 81.5}; }

void demo() {
    auto [count, best, avg] = stats();               // ثلاثة أسماء دفعة واحدة
    map<string, int> m = {{"أ", 1}};
    for (const auto& [key, value] : m) { /* ... */ }
    if (auto [it, inserted] = m.insert({"ب", 2}); inserted) { /* أُضيف */ }
}

لاحظ if (init; condition) — تعريف متغير يعيش داخل الشرط فقط (C++17).

std::variant: واحد من عدة أنواع

variant<int, string> يحمل إما int أو string، ويعرف أيهما — بديل آمن لـ union:

#include <iostream>
#include <string>
#include <variant>
using namespace std;

int main() {
    variant<int, string> v = 42;
    cout << get<int>(v) << '\n';
    v = string("نص");
    cout << boolalpha << holds_alternative<string>(v) << '\n';   // true
    if (auto p = get_if<int>(&v)) cout << *p;                     // لا يُطبع: ليست int الآن
    // get<int>(v) هنا يرمي bad_variant_access
}

visit: التعامل مع كل الاحتمالات

std::visit تستدعي دالة بالنوع الموجود فعلاً. مع lambda عامة و if constexpr:

#include <type_traits>
#include <variant>

struct Circle { double r; };
struct Rect { double w, h; };
using Shape = std::variant<Circle, Rect>;

double area(const Shape& s) {
    return std::visit([](const auto& sh) -> double {
        using T = std::decay_t<decltype(sh)>;
        if constexpr (std::is_same_v<T, Circle>) return 3.14159 * sh.r * sh.r;
        else return sh.w * sh.h;
    }, s);
}

variant مقابل الوراثة: variant مناسب عندما تكون الأنواع معروفة ومغلقة (لا يضيف أحد أنواعاً جديدة) — بلا مؤشرات ولا heap ولا virtual. الوراثة مناسبة عندما تريد أن يضيف الآخرون أنواعاً جديدة.

std::string_view: نص بلا نسخ

const string& تجبر المستدعي على امتلاك std::string؛ تمرير "نص حرفي" يُنشئ string مؤقتاً. string_view "نافذة" على نص موجود (مؤشر + طول) تقبل أي نوع نص بلا نسخ:

#include <string_view>

std::size_t countSpaces(std::string_view text) {
    std::size_t n = 0;
    for (char c : text) if (c == ' ') ++n;
    return n;
}
// countSpaces("a b c")، countSpaces(myString)، countSpaces(myString.substr(2)) — كلها تعمل

⚠️ string_view لا تملك النص. لا تُعِدها من دالة تشير إلى string محلي، ولا تخزنها بعد موت النص الأصلي:

#include <string>
#include <string_view>

std::string_view bad() {
    std::string local = "مؤقت";
    return local;          // ❌ مرجع معلّق: local يموت هنا
}

استخدمها للمعاملات للقراءة فقط، وخزّن std::string في الأعضاء.

ملخص

| تحتاج | استخدم |

|---|---|

| قيمة قد تغيب | optional<T> |

| قيمة من مجموعة أنواع ثابتة | variant<A, B, C> + visit |

| إرجاع عدة قيم بأسماء | struct أو tuple + structured bindings |

| معامل نصي للقراءة فقط | string_view |

تمرين

اكتب optional<int> parseAge(string_view text) تعيد العمر إذا كان النص أرقاماً بين 0 و 150 فقط. ثم عرّف using Token = variant<int, string>; وقسّم جملة إلى tokens (أرقام ونصوص) واطبع كل token بنوعه باستخدام visit.

#include <iostream>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <variant>
#include <vector>
using namespace std;

// ===== optional: نتيجة قد تغيب =====
optional<int> parseAge(string_view text) {
    if (text.empty() || text.size() > 3) return nullopt;
    int value = 0;
    for (char c : text) {
        if (c < '0' || c > '9') return nullopt;
        value = value * 10 + (c - '0');
    }
    if (value > 150) return nullopt;
    return value;
}

// ===== variant: قيمة من أنواع محددة =====
struct Circle { double r; };
struct Rect { double w, h; };
struct Triangle { double base, height; };
using Shape = variant<Circle, Rect, Triangle>;

double area(const Shape& s) {
    return visit([](const auto& sh) -> double {
        using T = decay_t<decltype(sh)>;
        if constexpr (is_same_v<T, Circle>) return 3.14159 * sh.r * sh.r;
        else if constexpr (is_same_v<T, Rect>) return sh.w * sh.h;
        else return 0.5 * sh.base * sh.height;
    }, s);
}

string shapeName(const Shape& s) {
    switch (s.index()) {          // رقم النوع الحالي حسب ترتيبه في variant
        case 0: return "دائرة";
        case 1: return "مستطيل";
        default: return "مثلث";
    }
}

// ===== string_view: قراءة نص بلا نسخ =====
size_t countWords(string_view text) {
    size_t words = 0;
    bool inWord = false;
    for (char c : text) {
        if (c == ' ') inWord = false;
        else if (!inWord) { inWord = true; ++words; }
    }
    return words;
}

int main() {
    cout << "=== optional ===\n";
    for (string_view input : {"25", "abc", "200", "7"}) {
        auto age = parseAge(input);
        cout << input << " → " << (age ? to_string(*age) : string("غير صالح")) << "\n";
    }
    cout << "القيمة الافتراضية: " << parseAge("x").value_or(-1) << "\n";

    cout << "\n=== variant + visit ===\n";
    vector<Shape> shapes = {Circle{1}, Rect{2, 3}, Triangle{4, 5}};
    for (const auto& s : shapes) cout << shapeName(s) << ": " << area(s) << "\n";

    cout << "\n=== structured bindings ===\n";
    map<string, int> scores = {{"سارة", 91}, {"عمر", 78}};
    for (const auto& [name, score] : scores) cout << name << " = " << score << "\n";
    if (auto [it, inserted] = scores.insert({"ليلى", 85}); inserted)
        cout << "أُضيفت " << it->first << "\n";

    cout << "\n=== string_view ===\n";
    string sentence = "تعلم لغة C++ الحديثة خطوة خطوة";
    cout << "عدد الكلمات: " << countWords(sentence) << "\n";
    cout << "من نص حرفي: " << countWords("بدون نسخ أي شيء") << "\n";
    return 0;
}
اختبر فهمك