سؤال

لقد كنت أقرأ من خلال الأسئلة الشائعة حول لغة C++ وكان فضوليًا بشأن friend تصريح.أنا شخصيا لم أستخدمها أبدا، ولكنني مهتم باستكشاف اللغة.

ما هو مثال جيد للاستخدام friend?


قراءة الأسئلة الشائعة لفترة أطول قليلاً تعجبني فكرة << >> التحميل الزائد للمشغل وإضافته كصديق لتلك الفئات.لكنني لست متأكدًا من كيفية عدم كسر هذا التغليف.متى يمكن أن تظل هذه الاستثناءات ضمن نطاق الصرامة OOP؟

هل كانت مفيدة؟

المحلول

أولا (IMO) لا تستمع للأشخاص الذين يقولون friend ليس من المفيد.انه مفيد.في العديد من المواقف، سيكون لديك كائنات تحتوي على بيانات أو وظائف ليس المقصود منها أن تكون متاحة للعامة.وينطبق هذا بشكل خاص على قواعد التعليمات البرمجية الكبيرة التي تضم العديد من المؤلفين الذين قد يكونون على دراية سطحية بمجالات مختلفة فقط.

توجد بدائل لمحدد الصديق، ولكنها غالبًا ما تكون مرهقة (فئات ملموسة على مستوى cpp/أنواع محددة من الحروف المقنعة) أو غير مضمونة (التعليقات أو اصطلاحات اسم الوظيفة).

على الجواب؛

ال friend يسمح المحدد للفئة المعينة بالوصول إلى البيانات أو الوظائف المحمية داخل الفئة التي تصدر بيان الصديق.على سبيل المثال، في الكود أدناه، يمكن لأي شخص أن يسأل الطفل عن اسمه، ولكن يمكن للأم والطفل فقط تغيير الاسم.

يمكنك أن تأخذ هذا المثال البسيط إلى أبعد من ذلك من خلال النظر في فئة أكثر تعقيدًا مثل Window.من المحتمل جدًا أن تحتوي النافذة على العديد من عناصر الوظائف/البيانات التي لا ينبغي أن تكون متاحة للعامة، ولكنها ضرورية لفئة ذات صلة مثل WindowManager.

class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;

public:

  string name( void );

protected:

  void setName( string newName );
};

نصائح أخرى

في العمل نحن استخدم الأصدقاء لاختبار الكود, ، على نطاق واسع.هذا يعني أنه يمكننا توفير التغليف المناسب وإخفاء المعلومات لرمز التطبيق الرئيسي.ولكن يمكننا أيضًا الحصول على رمز اختبار منفصل يستخدم الأصدقاء لفحص الحالة الداخلية والبيانات للاختبار.

يكفي أن أقول إنني لن أستخدم الكلمة الأساسية "صديق" كعنصر أساسي في تصميمك.

ال friend الكلمة الرئيسية لديها عدد من الاستخدامات الجيدة.فيما يلي الاستخدامين اللذين يظهران لي على الفور:

تعريف الصديق

يسمح تعريف الصديق بتعريف وظيفة في نطاق الفئة، ولكن لن يتم تعريف الوظيفة كوظيفة عضو، ولكن كوظيفة مجانية لمساحة الاسم المتضمنة، ولن تكون مرئية بشكل طبيعي باستثناء البحث المعتمد على الوسيطة.وهذا يجعلها مفيدة بشكل خاص للتحميل الزائد للمشغل:

namespace utils {
    class f {
    private:
        typedef int int_type;
        int_type value;

    public:
        // let's assume it doesn't only need .value, but some
        // internal stuff.
        friend f operator+(f const& a, f const& b) {
            // name resolution finds names in class-scope. 
            // int_type is visible here.
            return f(a.value + b.value);
        }

        int getValue() const { return value; }
    };
}

int main() {
    utils::f a, b;
    std::cout << (a + b).getValue(); // valid
}

فئة أساسية CRTP خاصة

في بعض الأحيان، تجد ضرورة أن تحتاج السياسة إلى الوصول إلى الفئة المشتقة:

// possible policy used for flexible-class.
template<typename Derived>
struct Policy {
    void doSomething() {
        // casting this to Derived* requires us to see that we are a 
        // base-class of Derived.
        some_type const& t = static_cast<Derived*>(this)->getSomething();
    }
};

// note, derived privately
template<template<typename> class SomePolicy>
struct FlexibleClass : private SomePolicy<FlexibleClass> {
    // we derive privately, so the base-class wouldn't notice that, 
    // (even though it's the base itself!), so we need a friend declaration
    // to make the base a friend of us.
    friend class SomePolicy<FlexibleClass>;

    void doStuff() {
         // calls doSomething of the policy
         this->doSomething();
    }

    // will return useful information
    some_type getSomething();
};

ستجد مثالاً غير مفتعل لذلك في هذا إجابة.رمز آخر يستخدم ذلك موجود في هذا إجابة.تقوم قاعدة CRTP بإلقاء هذا المؤشر، لتتمكن من الوصول إلى حقول البيانات للفئة المشتقة باستخدام مؤشرات عضو البيانات.

@رو:لم يتم كسر التغليف هنا لأن الفصل نفسه يحدد من يمكنه الوصول إلى أعضائه الخاصين.لن يتم كسر التغليف إلا إذا كان من الممكن أن يحدث ذلك من خارج الفصل، على سبيل المثال.إذا كان لديك operator << سيعلن "أنا صديق للفصل foo.”

friend يحل محل استخدام public, ، عدم استخدام private!

في الواقع، الأسئلة الشائعة حول C++ يجيب على هذا بالفعل.

المثال الأساسي هو التحميل الزائد للمشغل <<.الاستخدام الشائع الآخر هو السماح للمساعد أو فئة المسؤول بالوصول إلى عناصرك الداخلية.

فيما يلي بعض الإرشادات التي سمعتها عن أصدقاء C++.هذا الأخير لا ينسى بشكل خاص.

  • أصدقاؤك ليسوا أصدقاء طفلك.
  • أصدقاء طفلك ليسوا أصدقائك.
  • يمكن للأصدقاء فقط لمس أعضائك الخاصة.

يحرر:قراءة الأسئلة الشائعة لفترة أطول قليلاً تعجبني فكرة التحميل الزائد للمشغل << >> وإضافته كصديق لتلك الفئات، لكنني لست متأكدًا من كيفية عدم كسر هذا التغليف

كيف يمكن كسر التغليف؟

يمكنك كسر التغليف عندما تسمح بذلك غير مقيد الوصول إلى عضو البيانات.خذ بعين الاعتبار الفئات التالية:

class c1 {
public:
  int x;
};

class c2 {
public:
  int foo();
private:
  int x;
};

class c3 {
  friend int foo();
private:
  int x;
};

c1 يكون بوضوح غير مغلفة.يمكن لأي شخص القراءة والتعديل x فيه.ليس لدينا وسيلة لفرض أي نوع من التحكم في الوصول.

c2 ومن الواضح مغلفة.لا يوجد وصول عام إليها x.كل ما يمكنك فعله هو الاتصال بالرقم foo الوظيفة التي تؤدي بعض العمليات ذات مغزى في الفصل.

c3؟هل هذا أقل تغليفاً؟هل يسمح بالوصول غير المقيد إلى x؟هل يسمح بالوصول إلى وظائف غير معروفة؟

لا.يسمح بدقة واحد وظيفة للوصول إلى الأعضاء الخاصين في الفصل.تماما مثل c2 فعل.ومثلما c2, ، الوظيفة الوحيدة التي يمكنها الوصول ليست "وظيفة عشوائية وغير معروفة"، ولكن "الوظيفة المدرجة في تعريف الفئة".تماما مثل c2, يمكننا أن نرى، بمجرد النظر إلى تعريفات الفئة، أ مكتمل قائمة من لديه حق الوصول.

إذًا كيف يكون هذا أقل تغليفًا؟نفس القدر من التعليمات البرمجية لديه حق الوصول إلى أعضاء الفصل الخاصين.و الجميع يتم إدراج من لديه حق الوصول في تعريف الفصل.

friend لا يكسر التغليف.إنه يجعل بعض مبرمجي Java يشعرون بعدم الارتياح، لأنهم عندما يقولون "OOP"، فإنهم في الواقع يقصد "جافا".عندما يقولون "التغليف"، فإنهم لا يقصدون "يجب حماية الأعضاء الخاصين من الوصول التعسفي"، ولكن "فئة Java حيث الوظائف الوحيدة القادرة على الوصول إلى الأعضاء الخاصين، هي أعضاء الفصل"، على الرغم من أن هذا محض هراء لعدة أسباب.

أولاً، كما هو موضح بالفعل، فهو مقيد للغاية.لا يوجد سبب يمنع السماح لأساليب الصداقة بفعل الشيء نفسه.

ثانيا، أنها ليست مقيدة كافٍ.النظر في فئة رابعة:

class c4 {
public:
  int getx();
  void setx(int x);
private:
  int x;
};

هذا، وفقًا لعقلية جافا المذكورة، تم تغليفه بشكل مثالي.ومع ذلك، فهو يسمح لأي شخص بقراءة وتعديل x.كيف يمكن ان يجعل الاحساس؟(تَلمِيح:لا)

الحد الأدنى:التغليف يدور حول القدرة على التحكم في الوظائف التي يمكنها الوصول إلى الأعضاء الخاصين.إنها لا حول المكان الذي توجد فيه تعريفات هذه الوظائف على وجه التحديد.

نسخة أخرى شائعة من مثال أندرو، مقطع الكود المخيف

parent.addChild(child);
child.setParent(parent);

بدلاً من القلق إذا كان كلا الخطين يتم تنفيذهما معًا دائمًا وبترتيب متسق، يمكنك جعل الطرق خاصة ويكون لديك وظيفة صديق لفرض الاتساق:

class Parent;

class Object {
private:
    void setParent(Parent&);

    friend void addChild(Parent& parent, Object& child);
};

class Parent : public Object {
private:
     void addChild(Object& child);

     friend void addChild(Parent& parent, Object& child);
};

void addChild(Parent& parent, Object& child) {
    if( &parent == &child ){ 
        wetPants(); 
    }
    parent.addChild(child);
    child.setParent(parent);
}

بمعنى آخر، يمكنك إبقاء الواجهات العامة أصغر حجمًا وفرض الثوابت التي تتقاطع مع الفئات والكائنات في وظائف الأصدقاء.

هل تتحكم في حقوق الوصول للأعضاء والوظائف باستخدام الحق الخاص/المحمي/العام؟لذا، بافتراض أن فكرة كل مستوى من هذه المستويات الثلاثة واضحة، فمن المفترض أن يكون واضحًا أننا نفتقد شيئًا ما...

على سبيل المثال، يعتبر إعلان العضو/الوظيفة محميًا أمرًا عامًا جدًا.أنت تقول أن هذه الوظيفة بعيدة المنال الجميع (باستثناء الطفل الموروث بالطبع).ولكن ماذا عن الاستثناءات؟يتيح لك كل نظام أمان الحصول على نوع من "القائمة البيضاء" أليس كذلك؟

لذا صديق يتيح لك المرونة في عزل الأجسام الصلبة، ولكنه يسمح بإنشاء "ثغرة" للأشياء التي تشعر أنها مبررة.

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

إنه لا يفيد حقًا، بخلاف السماح لك بالوصول إلى متغير العضو دون استخدام وظيفة الإعداد

حسنًا، هذه ليست الطريقة الصحيحة للنظر إليها.والفكرة هي التحكم في من يستطيع الوصول إلى ما، سواء كان لديه أم لا وظيفة الإعداد لا علاقة له به.

لقد وجدت مكانًا مفيدًا لاستخدام وصول الأصدقاء:وحدة الوظائف الخاصة.

يكون Friend مفيدًا عندما تقوم بإنشاء حاوية وتريد تنفيذ مكرر لتلك الفئة.

الجواب القصير سيكون:يستخدم صديق عندما يكون في الواقع يتحسن التغليف.يعد تحسين سهولة القراءة وسهولة الاستخدام (المشغلان << و >> المثال الأساسي) سببًا وجيهًا أيضًا.

أما بالنسبة لأمثلة تحسين التغليف، فإن الفئات المصممة خصيصًا للعمل مع الأجزاء الداخلية للفئات الأخرى (تتبادر إلى ذهنك فئات الاختبار) هي مرشحة جيدة.

يقول منشئ C++ أن هذا لا يخرق أي مبدأ تغليف، وسأقتبس منه:

هل ينتهك "الصديق" التغليف؟لا.لم يحدث ذلك."الصديق" هو ​​آلية صريحة لمنح حق الوصول، تمامًا مثل العضوية.لا يمكنك (في برنامج المطابقة القياسي) منح نفسك حق الوصول إلى الفصل الدراسي دون تعديل مصدره.

الأمر أكثر من واضح...

لقد واجهتنا مشكلة مثيرة للاهتمام في شركة كنت أعمل فيها سابقًا حيث استخدمنا صديقًا لإحداث تأثير لائق.لقد عملت في قسم إطار العمل لدينا وقمنا بإنشاء نظام أساسي لمستوى المحرك على نظام التشغيل المخصص لدينا.داخليًا كان لدينا هيكل طبقي:

         Game
        /    \
 TwoPlayer  SinglePlayer

كانت كل هذه الفئات جزءًا من الإطار ويحتفظ بها فريقنا.الألعاب التي تنتجها الشركة بنيت على رأس هذا الإطار المستمد من إحدى ألعاب الأطفال.كانت المشكلة هي أن اللعبة تحتوي على واجهات لأشياء مختلفة يحتاج SinglePlayer وTwoPlayer إلى الوصول إليها ولكننا لا نريد الكشف عنها خارج فئات الإطار.كان الحل هو جعل هذه الواجهات خاصة والسماح لـ TwoPlayer وSinglePlayer بالوصول إليها عبر الصداقة.

بصدق، كان من الممكن حل هذه المشكلة برمتها من خلال تطبيق أفضل لنظامنا ولكننا كنا مقيدين بما لدينا.

استخدام آخر: صديق (+ الميراث الظاهري) يمكن استخدامه لتجنب الاشتقاق من فئة (ويعرف أيضًا باسم:"اجعل الفصل أقل من المستوى") => 1, 2

من 2:

 class Fred;

 class FredBase {
 private:
   friend class Fred;
   FredBase() { }
 };

 class Fred : private virtual FredBase {
 public:
   ...
 }; 

للقيام بـ TDD عدة مرات استخدمت الكلمة الأساسية "صديق" في C++.

هل يمكن لصديق أن يعرف كل شيء عني؟


محدث:لقد وجدت هذه الإجابة القيمة حول الكلمة الرئيسية "صديق" من موقع بيارن ستروستروب.

"الصديق" هو ​​آلية صريحة لمنح حق الوصول، تمامًا مثل العضوية.

فيما يتعلق بالمشغل<< والمشغل>>، لا يوجد سبب وجيه لجعل هؤلاء المشغلين أصدقاء.صحيح أنهم لا ينبغي أن يكونوا من وظائف الأعضاء، لكن لا يلزمهم أن يكونوا أصدقاء أيضًا.

أفضل ما يمكنك فعله هو إنشاء وظائف الطباعة العامة (ostream&) والقراءة (istream&).ثم اكتب عامل التشغيل<< وعامل التشغيل>> فيما يتعلق بهذه الوظائف.وهذا يعطي فائدة إضافية تتمثل في السماح لك بجعل هذه الوظائف افتراضية، مما يوفر تسلسلًا افتراضيًا.

أنا أستخدم كلمة الصديق الرئيسية فقط لاختبار الوظائف المحمية.سيقول البعض أنه لا ينبغي عليك اختبار الوظائف المحمية.ومع ذلك، أجد هذه الأداة مفيدة جدًا عند إضافة وظائف جديدة.

ومع ذلك، لا أستخدم الكلمة الأساسية مباشرةً في إعلانات الفصل الدراسي، وبدلاً من ذلك أستخدم أداة اختراق قالب أنيقة لتحقيق ذلك:

template<typename T>
class FriendIdentity {
public:
  typedef T me;
};

/**
 * A class to get access to protected stuff in unittests. Don't use
 * directly, use friendMe() instead.
 */
template<class ToFriend, typename ParentClass>
class Friender: public ParentClass
{
public:
  Friender() {}
  virtual ~Friender() {}
private:
// MSVC != GCC
#ifdef _MSC_VER
  friend ToFriend;
#else
  friend class FriendIdentity<ToFriend>::me;
#endif
};

/**
 * Gives access to protected variables/functions in unittests.
 * Usage: <code>friendMe(this, someprotectedobject).someProtectedMethod();</code>
 */
template<typename Tester, typename ParentClass>
Friender<Tester, ParentClass> & 
friendMe(Tester * me, ParentClass & instance)
{
    return (Friender<Tester, ParentClass> &)(instance);
}

وهذا يتيح لي القيام بما يلي:

friendMe(this, someClassInstance).someProtectedFunction();

يعمل على دول مجلس التعاون الخليجي وMSVC على الأقل.

عليك أن تكون حذرًا جدًا بشأن متى/أين تستخدم friend الكلمة الرئيسية، وأنا مثلك، نادرًا ما استخدمتها.فيما يلي بعض الملاحظات حول الاستخدام friend والبدائل.

لنفترض أنك تريد مقارنة شيئين لمعرفة ما إذا كانا متساويين.يمكنك إما:

  • استخدم طرق الوصول لإجراء المقارنة (تحقق من كل ivar وحدد المساواة).
  • أو يمكنك الوصول إلى جميع الأعضاء مباشرةً عن طريق نشرهم للعامة.

المشكلة في الخيار الأول هي أنه قد يكون هناك الكثير من أدوات الوصول، وهي أبطأ (قليلاً) من الوصول المتغير المباشر، وأصعب في القراءة، ومرهقة.المشكلة في الطريقة الثانية هي أنك تكسر التغليف تمامًا.

سيكون الأمر رائعًا إذا تمكنا من تحديد وظيفة خارجية لا يزال بإمكانها الوصول إلى الأعضاء الخاصين في الفصل.يمكننا أن نفعل هذا مع friend الكلمة الرئيسية:

class Beer {
public:
    friend bool equal(Beer a, Beer b);
private:
    // ...
};

طريقة equal(Beer, Beer) الآن لديه إمكانية الوصول المباشر إلى a و bالأعضاء الخاصين (والتي قد تكون char *brand, float percentAlcohol, ، إلخ.هذا مثال مفتعل إلى حد ما، سوف تطبقه عاجلاً friend إلى مثقلة == operator, ، لكننا سنصل إلى ذلك.

هناك عدد قليل من الأشياء ملاحظة:

  • أ friend ليست وظيفة عضو في الفصل
  • إنها وظيفة عادية تتمتع بوصول خاص إلى أعضاء الفصل الخاصين
  • لا تستبدل جميع الملحقات والمتحولات بالأصدقاء (يمكنك أيضًا صنع كل شيء public!)
  • الصداقة ليست متبادلة
  • الصداقة ليست متعدية
  • الصداقة لا تورث
  • أو، كما توضح الأسئلة الشائعة لـ C++:"لمجرد منحك حق الوصول إلى صداقتي، لا يمنح أطفالك حق الوصول إلي تلقائيًا، ولا يمنح أصدقاءك تلقائيًا حق الوصول إلي، ولا يمنحني تلقائيًا حق الوصول إليك."

أنا فقط استخدم حقا friends عندما يكون من الصعب جدًا القيام بذلك بالطريقة الأخرى.وكمثال آخر، غالبًا ما يتم إنشاء العديد من وظائف الرياضيات المتجهة كـ friends بسبب قابلية التشغيل البيني لـ Mat2x2, Mat3x3, Mat4x4, Vec2, Vec3, Vec4, ، إلخ.ومن الأسهل كثيرًا أن نكون أصدقاء، بدلاً من الاضطرار إلى استخدام أدوات الوصول في كل مكان.كما أشار، friend غالبًا ما يكون مفيدًا عند تطبيقه على << (مفيد حقًا لتصحيح الأخطاء) ، >> وربما == عامل التشغيل، ولكن يمكن استخدامه أيضًا لشيء مثل هذا:

class Birds {
public:
    friend Birds operator +(Birds, Birds);
private:
    int numberInFlock;
};


Birds operator +(Birds b1, Birds b2) {
    Birds temp;
    temp.numberInFlock = b1.numberInFlock + b2.numberInFlock;
    return temp;
}

كما أقول، أنا لا تستخدم friend في كثير من الأحيان على الإطلاق، ولكن بين الحين والآخر هو ما تحتاجه.أتمنى أن يساعدك هذا!

مثال الشجرة هو مثال جيد جدًا:وجود كائن في بعض الفئة المختلفة دون وجود علاقة ميراث.

ربما قد تحتاج أيضًا إلى أن يكون مُنشئًا محميًا ويجبر الناس على استخدام مصنع "صديقك".

...حسنًا، بصراحة يمكنك العيش بدونها.

للقيام بـ TDD عدة مرات استخدمت الكلمة الأساسية "صديق" في C++.
هل يمكن لصديق أن يعرف كل شيء عني؟

لا إنها صداقة من طرف واحد :"(

مثال واحد محدد حيث أستخدمه friend هو عند الخلق سينجلتون الطبقات.ال friend تتيح لي الكلمة الأساسية إنشاء وظيفة موصل، وهي أكثر إيجازًا من وجود طريقة "GetInstance ()" دائمًا في الفصل.

/////////////////////////
// Header file
class MySingleton
{
private:
    // Private c-tor for Singleton pattern
    MySingleton() {}

    friend MySingleton& GetMySingleton();
}

// Accessor function - less verbose than having a "GetInstance()"
//   static function on the class
MySingleton& GetMySingleton();


/////////////////////////
// Implementation file
MySingleton& GetMySingleton()
{
    static MySingleton theInstance;
    return theInstance;
}

توفر وظائف وفئات الأصدقاء إمكانية الوصول المباشر إلى أعضاء الفئة الخاصين والمحميين لتجنب كسر التغليف في الحالة العامة.معظم الاستخدام يكون مع ostream:نود أن نكون قادرين على كتابة:

Point p;
cout << p;

ومع ذلك، قد يتطلب هذا الوصول إلى البيانات الخاصة للنقطة، لذلك قمنا بتعريف المشغل المحمل بشكل زائد

friend ostream& operator<<(ostream& output, const Point& p);

ومع ذلك، هناك آثار تغليف واضحة.أولاً، تتمتع فئة الصديق أو الوظيفة الآن بإمكانية الوصول الكامل إلى جميع أعضاء الفئة، حتى أولئك الذين لا يتعلقون باحتياجاتها.ثانيًا، أصبحت تطبيقات الفصل والصديق الآن متشابكة إلى درجة يمكن أن يؤدي فيها التغيير الداخلي في الفصل إلى كسر الصديق.

إذا كنت تنظر إلى الصديق باعتباره امتدادًا للفصل الدراسي، فهذه ليست مشكلة من الناحية المنطقية.ولكن، في هذه الحالة، لماذا كان من الضروري قتل الصديق في المقام الأول.

لتحقيق نفس الشيء الذي يزعم "الأصدقاء" تحقيقه، ولكن دون كسر التغليف، يمكن للمرء القيام بذلك:

class A
{
public:
    void need_your_data(B & myBuddy)
    {
        myBuddy.take_this_name(name_);
    }
private:
    string name_;
};

class B
{
public:
    void print_buddy_name(A & myBuddy)
    {
        myBuddy.need_your_data(*this);
    }
    void take_this_name(const string & name)
    {
        cout << name;
    }
}; 

لم يتم كسر التغليف، والفئة B ليس لديها إمكانية الوصول إلى التنفيذ الداخلي في A، ومع ذلك فإن النتيجة هي نفسها كما لو أننا أعلنا أن B صديق لـ A.سيقوم المترجم بتحسين استدعاءات الوظائف، لذلك سيؤدي ذلك إلى نفس تعليمات الوصول المباشر.

أعتقد أن استخدام "صديق" هو ​​مجرد اختصار له فائدة يمكن الجدال فيها، ولكن بتكلفة محددة.

في C++، تعد الكلمة الأساسية "friend" مفيدة في التحميل الزائد للمشغل وإنشاء الجسر.

1.) الكلمة الأساسية "صديق" في التحميل الزائد للمشغل:
مثال على التحميل الزائد للمشغل هو:لنفترض أن لدينا فئة "Point" تحتوي على متغيرين عائمين
"x" (لإحداثي x) و"y" (لإحداثي y).الآن علينا أن نثقل كاهلنا "<<"(مشغل الاستخراج) بحيث إذا اتصلنا "cout << pointobj" ثم سيتم طباعة إحداثيات x و y (حيث يكون pointobj كائنًا من فئة Point).للقيام بذلك لدينا خياران:

   1.Overload "operator <<()" function in "ostream" class.
   2.Overload "operator<<()" function in "Point" class.
الآن الخيار الأول ليس جيدًا لأنه إذا أردنا زيادة التحميل على هذا العامل مرة أخرى لبعض الفئات المختلفة، فسيتعين علينا إجراء تغيير مرة أخرى في فئة "ostream".
ولهذا السبب الثاني هو الخيار الأفضل.الآن يمكن للمترجم الاتصال"operator <<()" وظيفة:

   1.Using ostream object cout.As: cout.operator<<(Pointobj) (form ostream class).
2.Call without an object.As: operator<<(cout, Pointobj) (from Point class).

لأننا قمنا بتنفيذ التحميل الزائد في فئة النقطة.لذا، لاستدعاء هذه الوظيفة بدون كائن علينا إضافته"friend" الكلمة الأساسية لأنه يمكننا استدعاء وظيفة صديق بدون كائن.الآن سيكون إعلان الوظيفة على النحو التالي:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"

2.) الكلمة الرئيسية "صديق" في صنع الجسر:
لنفترض أنه يتعين علينا إنشاء وظيفة يتعين علينا من خلالها الوصول إلى عضو خاص من فئتين أو أكثر (يُطلق عليه عمومًا اسم "الجسر").كيف نفعل ذلك:
للوصول إلى عضو خاص في الفصل، يجب أن يكون عضوًا في هذا الفصل.الآن للوصول إلى عضو خاص في فئة أخرى، يجب على كل فئة أن تعلن أن هذه الوظيفة هي وظيفة صديق.على سبيل المثال :لنفترض أن هناك فئتين A و B.وظيفة "funcBridge()" تريد الوصول إلى عضو خاص في كلا الفئتين.ثم يجب أن يعلن كلا الفصلين "funcBridge()" مثل:
friend return_type funcBridge(A &a_obj, B & b_obj);

أعتقد أن هذا من شأنه أن يساعد في فهم الكلمة الرئيسية للأصدقاء.

كمرجع ل إعلان صديق يقول:

يظهر إعلان الصديق في نص الفصل ويمنح وظيفة أو فئة أخرى حق الوصول إليها خاصة ومحمية أعضاء الفصل الذي يظهر فيه إعلان الصديق.

لذا للتذكير فقط، هناك أخطاء فنية في بعض الإجابات التي تقول ذلك friend يمكن زيارتها فقط محمي أعضاء.

عند تنفيذ خوارزميات الشجرة للفئة، فإن رمز الإطار الذي قدمه لنا الأستاذ يحتوي على فئة الشجرة كصديق لفئة العقدة.

إنه لا يفيد حقًا، بخلاف السماح لك بالوصول إلى متغير العضو دون استخدام وظيفة الإعداد.

يمكنك استخدام الصداقة عندما تستخدم فئات مختلفة (لا ترث واحدة من الأخرى) أعضاء خاصين أو محميين من الفئة الأخرى.

حالات الاستخدام النموذجية لوظائف الأصدقاء هي عمليات يتم إجراؤها بين فئتين مختلفتين للوصول إلى أعضاء خاصين أو محميين من كليهما.

من http://www.cplusplus.com/doc/tutorial/inheritance/ .

يمكنك رؤية هذا المثال حيث تصل طريقة غير الأعضاء إلى الأعضاء الخاصين في الفصل.يجب الإعلان عن هذه الطريقة في هذا الفصل بالذات كصديق للفصل.

// friend functions
#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    Rectangle() {}
    Rectangle (int x, int y) : width(x), height(y) {}
    int area() {return width * height;}
    friend Rectangle duplicate (const Rectangle&);
};

Rectangle duplicate (const Rectangle& param)
{
  Rectangle res;
  res.width = param.width*2;
  res.height = param.height*2;
  return res;
}

int main () {
  Rectangle foo;
  Rectangle bar (2,3);
  foo = duplicate (bar);
  cout << foo.area() << '\n';
  return 0;
}

ربما فاتني شيء ما من الإجابات أعلاه ولكن هناك مفهوم مهم آخر في التغليف وهو إخفاء التنفيذ.يتيح تقليل الوصول إلى أعضاء البيانات الخاصة (تفاصيل تنفيذ الفصل) تعديل الكود بشكل أسهل لاحقًا.إذا قام صديق بالوصول مباشرة إلى البيانات الخاصة، فإن أي تغييرات في حقول بيانات التنفيذ (البيانات الخاصة)، ستكسر رمز الوصول إلى تلك البيانات.يؤدي استخدام طرق الوصول إلى التخلص من هذا في الغالب.مهم إلى حد ما على ما أعتقد.

قد لا يكون هذا موقف حالة استخدام فعلي ولكنه قد يساعد في توضيح استخدام الصديق بين الفصول.

النادي

class ClubHouse {
public:
    friend class VIPMember; // VIP Members Have Full Access To Class
private:
    unsigned nonMembers_;
    unsigned paidMembers_;
    unsigned vipMembers;

    std::vector<Member> members_;
public:
    ClubHouse() : nonMembers_(0), paidMembers_(0), vipMembers(0) {}

    addMember( const Member& member ) { // ...code }   
    void updateMembership( unsigned memberID, Member::MembershipType type ) { // ...code }
    Amenity getAmenity( unsigned memberID ) { // ...code }

protected:
    void joinVIPEvent( unsigned memberID ) { // ...code }

}; // ClubHouse

فئة الأعضاء

class Member {
public:
    enum MemberShipType {
        NON_MEMBER_PAID_EVENT,   // Single Event Paid (At Door)
        PAID_MEMBERSHIP,         // Monthly - Yearly Subscription
        VIP_MEMBERSHIP,          // Highest Possible Membership
    }; // MemberShipType

protected:
    MemberShipType type_;
    unsigned id_;
    Amenity amenity_;
public:
    Member( unsigned id, MemberShipType type ) : id_(id), type_(type) {}
    virtual ~Member(){}
    unsigned getId() const { return id_; }
    MemberShipType getType() const { return type_; }
    virtual void getAmenityFromClubHouse() = 0       
};

class NonMember : public Member {
public:
   explicit NonMember( unsigned id ) : Member( id, MemberShipType::NON_MEMBER_PAID_EVENT ) {}   

   void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class PaidMember : public Member {
public:
    explicit PaidMember( unsigned id ) : Member( id, MemberShipType::PAID_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class VIPMember : public Member {
public:
    friend class ClubHouse;
public:
    explicit VIPMember( unsigned id ) : Member( id, MemberShipType::VIP_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }

    void attendVIPEvent() {
        ClubHouse::joinVIPEvent( this->id );
    }
};

وسائل الراحة

class Amenity{};

إذا نظرت إلى العلاقة بين هذه الطبقات هنا؛يحتوي ClubHouse على مجموعة متنوعة من أنواع العضويات المختلفة وإمكانية الوصول إلى العضوية.يتم اشتقاق جميع الأعضاء من فئة فائقة أو أساسية حيث أنهم جميعًا يشتركون في معرف ونوع محدد شائع ويمكن للفئات الخارجية الوصول إلى معرفاتهم وأنواعهم من خلال وظائف الوصول الموجودة في الفئة الأساسية.

ولكن من خلال هذا النوع من التسلسل الهرمي للأعضاء والفئات المشتقة منها وعلاقتهم بفئة ClubHouse، فإن الفئة الوحيدة من الفئات المشتقة التي تتمتع "بامتيازات خاصة" هي فئة VIPMember.لا يمكن للفئة الأساسية والفئتين المشتقتين الأخريين الوصول إلى طريقة joinVIPEvent() الخاصة بـ ClubHouse، ومع ذلك تتمتع فئة VIP Member بهذا الامتياز كما لو كان لديها حق الوصول الكامل إلى هذا الحدث.

لذلك، مع VIPMember وClubHouse، يكون الوصول إلى طريق ذو اتجاهين حيث تكون فئات الأعضاء الأخرى محدودة.

أنت استطاع الالتزام بمبادئ OOP الأكثر صرامة وأنقى والتأكد من عدم وجود أي بيانات لدى أي أعضاء في أي فئة الملحقات بحيث تكون جميع الكائنات يجب أن نكون الوحيدين الذين يمكنهم معرفة بياناتهم والطريقة الوحيدة للتصرف بناءً عليها هي من خلال الطريقة غير المباشرة رسائل, ، أي: الأساليب.

ولكن حتى C# لديه داخلي الكلمة الأساسية للرؤية وجافا لها الكلمة الافتراضية طَرد إمكانية الوصول إلى المستوى لبعض الأشياء.تقترب لغة C++ بالفعل من OOP المثالي من خلال تقليل التنازل عن الرؤية إلى الفصل الدراسي عن طريق التحديد بالضبط أي فئة أخرى و فقط يمكن للفئات الأخرى رؤية ذلك.

أنا لا أستخدم C++ حقًا ولكن إذا كان C# موجودًا صديقأود ذلك بدلاً من التجميع العالمي داخلي المعدل، والذي أستخدمه كثيرًا بالفعل.إنه لا يكسر التغليف حقًا، لأن وحدة النشر في .NET يكون جمعية.

ولكن بعد ذلك هناك الداخليةVisibleToالسمة (otherAssembly) التي تعمل مثل التجميع المتقاطع صديق آلية.تستخدم Microsoft هذا للمرئية مصمم الجمعيات.

الأصدقاء مفيدون أيضًا لعمليات الاسترجاعات.يمكنك تنفيذ عمليات الاسترجاعات كطرق ثابتة

class MyFoo
{
private:
    static void callback(void * data, void * clientData);
    void localCallback();
    ...
};

أين callback المكالمات localCallback داخليا، و clientData لديه مثيل الخاص بك في ذلك.في رأيي،

أو...

class MyFoo
{
    friend void callback(void * data, void * callData);
    void localCallback();
}

ما يسمح به هذا هو أن يتم تعريف الصديق بشكل بحت في cpp كوظيفة على النمط c، وليس فوضى الفصل.

وبالمثل، فإن النمط الذي رأيته كثيرًا هو وضع كل حقًا أعضاء خاصون من فئة إلى فئة أخرى، والتي تم الإعلان عنها في الرأس، والمحددة في CPP، والأصدقاء.يسمح هذا للمبرمج بإخفاء الكثير من التعقيد والعمل الداخلي للفصل عن مستخدم الرأس.

في الرأس:

class MyFooPrivate;
class MyFoo
{
    friend class MyFooPrivate;
public:
    MyFoo();
    // Public stuff
private:
    MyFooPrivate _private;
    // Other private members as needed
};

في سي بي بي،

class MyFooPrivate
{
public:
   MyFoo *owner;
   // Your complexity here
};

MyFoo::MyFoo()
{
    this->_private->owner = this;
}

يصبح من الأسهل إخفاء الأشياء التي لا يحتاج المصب إلى رؤيتها بهذه الطريقة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top