Kodax

Static Members — OOP في C++

الأعضاء الثابتة (Static Members)

العضو العادي له نسخة في كل كائن. العضو static له نسخة واحدة للصنف كله يشترك فيها الجميع، وموجود حتى لو لم يُنشأ أي كائن.

بيانات static

#include <iostream>
using namespace std;

class Ticket {
    inline static int nextNumber = 1;   // C++17: تعريف وتهيئة داخل الصنف
    int number;
public:
    Ticket() : number(nextNumber++) {}
    int get() const { return number; }
};

int main() {
    Ticket a, b, c;
    cout << a.get() << b.get() << c.get() << '\n';   // 123
}

قبل C++17

كان يجب التصريح داخل الصنف ثم التعريف مرة واحدة في ملف .cpp:

class Config {
    static int maxUsers;          // تصريح فقط
public:
    static int getMax() { return maxUsers; }
};
int Config::maxUsers = 100;       // التعريف (وإلا: خطأ ربط undefined reference)

ستراه كثيراً في الكود القديم. في الكود الجديد استخدم inline static. الثوابت تُكتب static constexpr int MAX = 100; مباشرة.

دوال static

- تُستدعى عبر اسم الصنف: Ticket::count().

- لا تملك this، لذلك لا تصل إلى الأعضاء العادية إلا عبر كائن تمرره لها.

- استخدامات شائعة: عدادات، دوال مصنع (factory)، دوال مساعدة مرتبطة بالصنف.

#include <string>
using namespace std;

class Color {
    int r, g, b;
    Color(int r, int g, int b) : r(r), g(g), b(b) {}   // مُنشئ private
public:
    static Color red()   { return Color(255, 0, 0); }   // دوال مصنع بأسماء واضحة
    static Color fromGray(int v) { return Color(v, v, v); }
    string hex() const;
};

فخ: عدّاد الكائنات والنسخ

إذا عددت الكائنات بزيادة العداد في المُنشئ وإنقاصه في المدمّر، فإن النسخ يستدعي مُنشئ النسخ الافتراضي الذي لا يزيد العداد — فيصبح العدد خاطئاً (وقد يصبح سالباً). الحل: عرّف مُنشئ النسخ بنفسك، أو امنع النسخ بـ = delete (المثال الكامل يفعل ذلك).

متغيرات static المحلية

متغير static داخل دالة يُهيأ مرة واحدة عند أول استدعاء ويحتفظ بقيمته بين الاستدعاءات (والتهيئة آمنة مع الخيوط منذ C++11):

#include <iostream>
using namespace std;

int nextId() {
    static int id = 0;    // يُهيأ مرة واحدة فقط
    return ++id;
}

int main() {
    cout << nextId() << nextId() << nextId() << '\n';   // 123
}

متى تتجنب static؟

البيانات static المتغيرة هي عملياً متغيرات عامة مخفية داخل صنف: تجعل الاختبار أصعب وتسبب مشاكل مع الخيوط. استخدمها للعدادات والثوابت والمصانع، لا لتخزين حالة البرنامج كلها.

تمرين

أضف إلى صنف Ticket دالة static int issued() تعيد عدد التذاكر الصادرة، وامنع نسخ التذاكر (Ticket(const Ticket&) = delete;). جرّب نسخ تذكرة وشاهد رسالة المترجم.

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

class Employee {
    // C++17: inline static تسمح بالتهيئة داخل الصنف
    inline static int nextId = 1;
    inline static int activeCount = 0;
    inline static double totalSalary = 0;

    int id;
    string name;
    double salary;

public:
    static constexpr double MIN_SALARY = 500;   // ثابت مشترك

    Employee(string n, double s) : id(nextId++), name(move(n)), salary(s < MIN_SALARY ? MIN_SALARY : s) {
        ++activeCount;
        totalSalary += salary;
        cout << "✅ إنشاء: " << name << " (ID " << id << ")\n";
    }

    ~Employee() {
        --activeCount;
        totalSalary -= salary;
        cout << "❌ مغادرة: " << name << "\n";
    }

    // النسخ ممنوع: نسخة صامتة كانت ستُفسد activeCount و totalSalary
    Employee(const Employee&) = delete;
    Employee& operator=(const Employee&) = delete;

    void setSalary(double s) {
        if (s < MIN_SALARY) return;
        totalSalary += s - salary;
        salary = s;
    }

    const string& getName() const { return name; }

    // دوال static: لا this، تُستدعى عبر اسم الصنف
    static int count() { return activeCount; }
    static double averageSalary() { return activeCount ? totalSalary / activeCount : 0; }

    // دوال مصنع تعيد unique_ptr: الذاكرة تُحرر تلقائياً
    static unique_ptr<Employee> manager(const string& n) { return make_unique<Employee>(n, 10000); }
    static unique_ptr<Employee> developer(const string& n) { return make_unique<Employee>(n, 7000); }
    static unique_ptr<Employee> intern(const string& n) { return make_unique<Employee>(n, 300); }
};

int main() {
    cout << "الموظفون الآن: " << Employee::count() << "\n\n";

    vector<unique_ptr<Employee>> staff;
    staff.push_back(Employee::manager("أحمد"));
    staff.push_back(Employee::developer("محمد"));
    staff.push_back(Employee::developer("سارة"));
    staff.push_back(Employee::intern("علي"));   // الراتب يُرفع إلى الحد الأدنى 500

    cout << "\nالعدد: " << Employee::count() << "\n";
    cout << "المتوسط: " << Employee::averageSalary() << "\n";

    staff[1]->setSalary(8000);
    cout << "المتوسط بعد ترقية محمد: " << Employee::averageSalary() << "\n\n";

    staff.pop_back();   // يُحذف آخر موظف تلقائياً
    cout << "العدد بعد مغادرة المتدرب: " << Employee::count() << "\n\n";

    // Employee copy = *staff[0];   // خطأ ترجمة: النسخ محذوف
    cout << "نهاية main — يُحذف الباقون تلقائياً:\n";
    return 0;
}
اختبر فهمك