Friend Classes و Functions — OOP في C++
الدوال والأصناف الصديقة (friend)
أحياناً تحتاج دالة ليست عضواً في الصنف إلى الوصول إلى أعضائه الخاصة. المثال الأشهر: operator<< (يجب أن يكون دالة حرة كما تعلمت). friend تمنح هذا الحق صراحةً ومن داخل الصنف.
دالة صديقة
#include <iostream>
using namespace std;
class Money {
long long cents;
public:
explicit Money(long long c) : cents(c) {}
// hidden friend: معرّفة داخل الصنف، تُوجد فقط عند استخدام Money
friend ostream& operator<<(ostream& os, const Money& m) {
return os << m.cents / 100 << "." << (m.cents % 100 < 10 ? "0" : "") << m.cents % 100;
}
friend Money operator+(const Money& a, const Money& b) { return Money(a.cents + b.cents); }
};
int main() {
Money a(1050), b(205);
cout << a + b << '\n'; // 12.55
}تعريف الدالة الصديقة داخل الصنف (hidden friend) هو الأسلوب الحديث المفضل للعوامل: لا تلوث النطاق العام، ويجدها المترجم عند الحاجة فقط.
صنف صديق
class Engine {
int rpm = 0;
friend class Mechanic; // Mechanic يصل إلى rpm
};
class Mechanic {
public:
void tune(Engine& e) { e.rpm = 800; }
int read(const Engine& e) const { return e.rpm; }
};حدود الصداقة
- ليست متبادلة: إذا كان A صديقاً لـ B، فـ B ليس صديقاً لـ A تلقائياً.
- ليست متعدية: صديق صديقك ليس صديقك.
- لا تُورث: الأصناف المشتقة من Mechanic لا ترث صداقته.
- لا تتأثر بـ public/private: مكان كتابة friend داخل الصنف لا يهم.
هل friend تكسر التغليف؟
ليس إذا استُخدمت بحكمة: الصنف هو من يقرر من يثق به، والقائمة مكتوبة داخله. الدالة الصديقة جزء من واجهة الصنف مثل الدوال الأعضاء تماماً. لكن إذا احتجت friend كثيراً بين صنفين، فربما يجب دمجهما أو إعادة تقسيم المسؤوليات.
الاستخدامات المعقولة
1. عوامل التدفق << و >>.
2. العوامل الثنائية المتماثلة التي تحتاج البيانات الخاصة.
3. صنفان مترابطان بشدة (مثل حاوية ومُكرِّرها iterator).
4. الاختبارات أحياناً.
تمرين
في المثال الكامل (المدقق Auditor والحساب البنكي)، حوّل دالة emergencyWithdraw إلى دالة عضو عامة في BankAccount مع شرط تحقق. أيهما أوضح في رأيك، ولماذا؟
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// Forward declaration
class BankAccount;
// Friend class للتدقيق
class Auditor {
public:
void audit(const BankAccount& account);
void fullReport(const BankAccount& account);
};
class BankAccount {
private:
string accountNumber;
string ownerName;
double balance;
string pin;
// سجل المعاملات (خاص)
struct Transaction {
string type;
double amount;
string date;
};
vector<Transaction> transactions;
public:
BankAccount(const string& num, const string& name, const string& p, double b)
: accountNumber(num), ownerName(name), pin(p), balance(b) {}
void deposit(double amount) {
balance += amount;
transactions.push_back({"إيداع", amount, "2024-01-15"});
}
void withdraw(double amount, const string& inputPin) {
if (inputPin != pin) {
cout << "PIN خاطئ!" << endl;
return;
}
if (amount > balance) {
cout << "رصيد غير كافٍ!" << endl;
return;
}
balance -= amount;
transactions.push_back({"سحب", amount, "2024-01-15"});
}
// Friend function للطباعة
friend ostream& operator<<(ostream& os, const BankAccount& acc);
// Friend class
friend class Auditor;
// Friend function منفردة
friend void emergencyWithdraw(BankAccount& acc, double amount);
};
// تحقيق Friend function
ostream& operator<<(ostream& os, const BankAccount& acc) {
os << "الحساب: " << acc.accountNumber << endl;
os << "المالك: " << acc.ownerName << endl;
os << "الرصيد: $" << acc.balance;
return os;
}
// تحقيق Friend class methods
void Auditor::audit(const BankAccount& account) {
cout << "\n🔍 تدقيق الحساب: " << account.accountNumber << endl;
cout << "الرصيد الحالي: $" << account.balance << endl;
cout << "عدد المعاملات: " << account.transactions.size() << endl;
// يمكن الوصول لـ PIN (للتدقيق فقط!)
cout << "PIN مُعيّن: " << (account.pin.empty() ? "لا" : "نعم") << endl;
}
void Auditor::fullReport(const BankAccount& account) {
cout << "\n📊 تقرير تفصيلي" << endl;
cout << "================" << endl;
cout << "رقم الحساب: " << account.accountNumber << endl;
cout << "صاحب الحساب: " << account.ownerName << endl;
cout << "الرصيد: $" << account.balance << endl;
cout << "\nسجل المعاملات:" << endl;
for (const auto& t : account.transactions) {
cout << " - " << t.type << ": $" << t.amount << " (" << t.date << ")" << endl;
}
}
// Friend function للطوارئ
void emergencyWithdraw(BankAccount& acc, double amount) {
cout << "\n⚠️ سحب طوارئ: $" << amount << endl;
acc.balance -= amount; // بدون PIN!
acc.transactions.push_back({"سحب طوارئ", amount, "2024-01-15"});
}
int main() {
BankAccount account("123456", "أحمد محمد", "1234", 5000);
// عمليات عادية
account.deposit(1000);
account.withdraw(500, "1234");
// طباعة باستخدام friend operator
cout << account << endl;
// تدقيق
Auditor auditor;
auditor.audit(account);
auditor.fullReport(account);
// سحب طوارئ
emergencyWithdraw(account, 1000);
cout << "\nالحالة النهائية:\n" << account << endl; // main ليست صديقة: نستخدم operator<<
return 0;
}اختبر فهمك