เมื่อใดที่คุณควรใช้ 'เพื่อน' ใน C ++


354

ฉันได้อ่านคำถามที่พบบ่อย C ++และอยากรู้เกี่ยวกับการfriendประกาศ ฉันเองไม่เคยใช้มันเป็นการส่วนตัว แต่ฉันสนใจที่จะสำรวจภาษา

ตัวอย่างที่ดีของการใช้friendคืออะไร


อ่านคำถามที่พบบ่อยอีกเล็กน้อยฉันชอบความคิดของ<< >>ผู้ประกอบการมากไปและเพิ่มเป็นเพื่อนของชั้นเรียนเหล่านั้น อย่างไรก็ตามฉันไม่แน่ใจว่าวิธีนี้จะไม่ทำลาย encapsulation ข้อยกเว้นเหล่านี้จะอยู่ภายในความเข้มงวดของ OOP เมื่อใด


5
ในขณะที่ฉันเห็นด้วยกับคำตอบว่าชั้นเรียนเพื่อนไม่จำเป็นต้องเป็นสิ่งเลวร้าย แต่ฉันมักจะถือว่ามันเป็นรหัสที่เล็ก บ่อยครั้งถึงแม้ว่าจะไม่เสมอไปก็ตามแสดงว่าลำดับชั้นของชั้นต้องได้รับการพิจารณาใหม่
Mawg กล่าวว่าคืนสถานะโมนิก้า

1
คุณจะใช้คลาสเพื่อนที่มีเพศสัมพันธ์แน่นแล้ว นั่นคือสิ่งที่มันถูกสร้างขึ้นมาเพื่อ ตัวอย่างเช่นตารางฐานข้อมูลและดัชนีนั้นมีการเชื่อมโยงกันอย่างแน่นหนา เมื่อตารางเปลี่ยนแปลงดัชนีทั้งหมดจะต้องได้รับการอัพเดต ดังนั้นคลาส DBIndex จะประกาศ DBTable เป็นเพื่อนเพื่อให้ DBTable สามารถเข้าถึงดัชนี internals โดยตรง แต่จะไม่มีส่วนต่อประสานสาธารณะกับ DBIndex มันไม่สมเหตุสมผลเลยที่จะอ่านดัชนี
shawnhcorey

OOP "ครูสอนศาสนา" ที่มีประสบการณ์ภาคปฏิบัติเพียงเล็กน้อยยืนยันว่าเพื่อนละเมิดหลักการ OOP เพราะชั้นเรียนควรเป็นผู้ดูแล แต่เพียงผู้เดียวของรัฐส่วนตัว สิ่งนี้เป็นสิ่งที่ดีจนกระทั่งคุณพบสถานการณ์ทั่วไปที่สองคลาสต้องการรักษาสถานะส่วนตัวที่ใช้ร่วมกัน
kaalus

คำตอบ:


335

ประการแรก (IMO) ไม่ฟังคนที่พูด friendไม่มีประโยชน์ มันมีประโยชน์ ในหลาย ๆ สถานการณ์คุณจะมีวัตถุที่มีข้อมูลหรือฟังก์ชันการทำงานที่ไม่ได้ตั้งใจให้เป็นแบบสาธารณะ นี่เป็นเรื่องจริงโดยเฉพาะอย่างยิ่งกับฐานรหัสขนาดใหญ่ที่มีผู้เขียนหลายคนซึ่งอาจคุ้นเคยกับพื้นที่ต่างกันเพียงผิวเผินเท่านั้น

มีทางเลือกให้กับตัวระบุเพื่อน แต่บ่อยครั้งที่พวกเขามีความยุ่งยาก (คลาสคอนกรีตระดับ cpp ระดับ / ประเภทที่สวมหน้ากาก) หรือไม่เข้าใจผิด (ความเห็นหรือการประชุมชื่อฟังก์ชั่น)

บนคำตอบ;

ตัวfriendระบุอนุญาตการเข้าถึงคลาสที่กำหนดให้กับข้อมูลหรือฟังก์ชันที่ได้รับการป้องกันภายในคลาสที่สร้างข้อความสั่งเพื่อน ตัวอย่างเช่นในรหัสด้านล่างทุกคนอาจขอให้เด็กคนหนึ่งชื่อของพวกเขา แต่เพียงแม่และเด็กอาจเปลี่ยนชื่อ

คุณสามารถนำตัวอย่างง่าย ๆ นี้ไปพิจารณาโดยการพิจารณาคลาสที่ซับซ้อนมากขึ้นเช่น Window มีแนวโน้มว่า 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 );
};

114
ในฐานะที่เป็นโน้ตเสริมคำถามที่พบบ่อย C ++ กล่าวถึงที่friend ช่วยเพิ่มการห่อหุ้ม friendให้สิทธิ์การเข้าถึงแก่สมาชิกเช่นเดียวกับที่protectedทำได้ การควบคุมอย่างละเอียดใด ๆ จะดีกว่าการอนุญาตให้เข้าถึงสาธารณะ ภาษาอื่น ๆ ที่กำหนดกลไกการเข้าถึงเลือกเกินไปพิจารณา C # internal's วิจารณ์เชิงลบส่วนใหญ่เกี่ยวกับการใช้งานfriendที่เกี่ยวข้องกับการแต่งงานกันแน่นซึ่งมักจะเห็นว่าเป็นสิ่งที่ไม่ดี อย่างไรก็ตามในบางกรณีการมีเพศสัมพันธ์ที่แน่นกว่านั้นเป็นสิ่งที่คุณต้องการและfriendให้พลังนั้นกับคุณ
André Caron

5
คุณช่วยพูดอะไรเพิ่มเติมเกี่ยวกับ (คลาสคอนกรีตระดับ cpp) และ (ชนิดที่สวมหน้ากาก), แอนดรูว์ได้ไหม?
OmarOthman

18
คำตอบนี้ดูเหมือนจะให้ความสำคัญกับการอธิบายสิ่งที่friendเป็นมากกว่าการเป็นตัวอย่างที่สร้างแรงบันดาลใจ ตัวอย่าง Window / WindowManager ดีกว่าตัวอย่างที่แสดง แต่คลุมเครือเกินไป คำตอบนี้ยังไม่ได้กล่าวถึงส่วนที่ห่อหุ้มของคำถาม
bames53

4
'เพื่อน' มีอยู่แล้วอย่างมีประสิทธิภาพเพราะ C ++ ไม่มีความคิดเกี่ยวกับแพคเกจที่สมาชิกทุกคนสามารถแชร์รายละเอียดการใช้งานได้? ฉันจะสนใจตัวอย่างจริง ๆ ของโลกจริงๆ
weberc2

1
ฉันคิดว่าแม่ / ลูกเป็นตัวอย่างที่โชคร้าย ชื่อเหล่านี้เหมาะสำหรับอินสแตนซ์ แต่ไม่ใช่สำหรับคลาส (ปัญหาแสดงว่าเรามีแม่ 2 คนและแต่ละคนมีลูกของตัวเอง)
โจดังนั้น

162

ที่ทำงานเราใช้เพื่อนเพื่อทดสอบโค้ดอย่างกว้างขวาง มันหมายความว่าเราสามารถจัดหาการห่อหุ้มและซ่อนข้อมูลที่เหมาะสมสำหรับรหัสแอปพลิเคชันหลัก แต่เราสามารถมีรหัสทดสอบแยกต่างหากที่ใช้เพื่อนเพื่อตรวจสอบสถานะภายในและข้อมูลสำหรับการทดสอบ

พอจะพูดได้ว่าฉันจะไม่ใช้คำหลักเพื่อนเป็นองค์ประกอบสำคัญในการออกแบบของคุณ


นั่นคือสิ่งที่ฉันใช้สำหรับ หรือเพียงแค่ตั้งค่าตัวแปรสมาชิกให้มีการป้องกัน น่าเสียดายที่มันไม่ได้ผลสำหรับ C ++ / CLI :-(
Jon Cage

12
โดยส่วนตัวฉันจะไม่สนับสนุนสิ่งนี้ โดยทั่วไปคุณกำลังทดสอบอินเทอร์เฟซเช่นชุดของอินพุตให้ชุดเอาต์พุตที่คาดหวัง ทำไมคุณต้องตรวจสอบข้อมูลภายใน
แกรม

55
@ Graeme: เพราะแผนการทดสอบที่ดีนั้นรวมถึงการทดสอบทั้งกล่องสีขาวและกล่องดำ
Ben Voigt

1
ฉันมักจะเห็นด้วยกับ @Graeme ตามที่อธิบายไว้อย่างสมบูรณ์ในคำตอบนี้
Alexis Leclerc

2
@ Graeme มันอาจไม่ใช่ข้อมูลภายในโดยตรง ฉันอาจเป็นวิธีที่ดำเนินการเฉพาะงานหรืองานในข้อมูลที่ซึ่งวิธีนั้นเป็นส่วนตัวของชั้นเรียนและไม่ควรเข้าถึงได้ในที่สาธารณะในขณะที่วัตถุอื่น ๆ อาจจำเป็นต้องให้อาหารหรือเมล็ดวิธีการป้องกันของชั้นเรียนด้วยข้อมูลของตัวเอง
Francis Cugler

93

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 ใช้ตัวชี้นี้เพื่อให้สามารถเข้าถึงเขตข้อมูลของคลาสที่ได้รับโดยใช้ data-member-pointers


สวัสดีฉันได้รับข้อผิดพลาดทางไวยากรณ์ (ใน xcode 4) เมื่อฉันลอง CRTP ของคุณ Xcode เชื่อว่าฉันพยายามสืบทอดเทมเพลตของคลาส ข้อผิดพลาดที่เกิดขึ้นP<C>ในtemplate<template<typename> class P> class C : P<C> {};เซน "ใช้แม่แบบเรียน C ต้องมีข้อโต้แย้งแม่" คุณมีปัญหาเดียวกันหรืออาจจะรู้วิธีแก้ปัญหาหรือไม่?
bennedich

@bennedich ได้อย่างรวดเร็วก่อนที่ดูเหมือนว่าข้อผิดพลาดชนิดที่คุณจะได้รับด้วยการสนับสนุนคุณสมบัติ C ++ ไม่เพียงพอ ซึ่งเป็นเรื่องธรรมดาในหมู่คอมไพเลอร์ การใช้FlexibleClassภายในFlexibleClassควรอ้างถึงชนิดของตัวเอง
Yakk - Adam Nevraumont

@bennedich: กฎการใช้ชื่อเทมเพลตของคลาสจากภายในเนื้อหาของคลาสเปลี่ยนเป็น C ++ 11 ลองเปิดใช้งานโหมด C ++ 11 ในคอมไพเลอร์ของคุณ
Ben Voigt

ใน Visual Studio 2015 เพิ่มสาธารณะนี้: f () {}; f (int_type t): value (t) {}; เพื่อป้องกันข้อผิดพลาดของคอมไพเลอร์นี้: error C2440: '<function-style-cast>': ไม่สามารถแปลงจาก 'utils :: f :: int_type' เป็น 'utils :: f' หมายเหตุ: ตัวสร้างไม่สามารถใช้ประเภทแหล่งที่มาหรือตัวสร้าง ความละเอียดเกินพิกัดไม่ชัดเจน
เดเมียนมี. ค.

41

@roo : การห่อหุ้มไม่ได้ถูกทำลายที่นี่เพราะชั้นเรียนกำหนดว่าใครสามารถเข้าถึงสมาชิกส่วนตัว การห่อหุ้มอาจจะพังก็ต่อเมื่อสิ่งนี้อาจเกิดขึ้นจากนอกห้องเรียนเช่นหากคุณoperator <<ต้องการประกาศว่า "ฉันเป็นเพื่อนในชั้นเรียนfoo"

friendแทนที่การใช้publicไม่ใช่การใช้private!

ที่จริง c ++ คำถามที่พบบ่อยคำตอบอย่างนี้แล้ว


14
"เพื่อนเข้ามาแทนที่การใช้งานของสาธารณะไม่ได้ใช้งานส่วนตัว!" ฉันสองว่า
Waleed Eissa

26
@ Asafaf: ใช่ แต่ FQA นั้นส่วนใหญ่แล้วความโกรธที่ไม่ต่อเนื่องกันเป็นจำนวนมากโดยไม่มีค่าใด ๆ ส่วนที่friendไม่มีข้อยกเว้น การสังเกตที่แท้จริงเพียงอย่างเดียวที่นี่คือ C ++ ช่วยให้แน่ใจว่า encapsulation ในเวลารวบรวมเท่านั้น และคุณไม่ต้องการคำพูดอีกแล้ว ส่วนที่เหลือเป็น bollocks ดังนั้นโดยสรุป: ส่วนนี้ของ FQA ไม่น่าพูดถึง
Konrad Rudolph

12
คำถามที่พบบ่อยๆส่วนใหญ่ที่เป็น BLX ที่สุด :)
พระราม-JKA Toti

1
@ Konrad: "การสังเกตที่แท้จริงเพียงอย่างเดียวที่นี่คือ C ++ ช่วยให้แน่ใจว่า encapsulation ที่รวบรวมเวลาเท่านั้น" ภาษาใดบ้างที่มั่นใจได้ในขณะใช้งานจริง? เท่าที่ฉันรู้การอ้างอิงกลับไปยังสมาชิกส่วนตัว (และฟังก์ชั่นสำหรับภาษาที่อนุญาตให้พอยน์เตอร์ไปยังฟังก์ชั่นหรือฟังก์ชั่นเป็นวัตถุชั้นหนึ่ง) ได้รับอนุญาตใน C #, Java, Python และอื่น ๆ
André Caron

@ André: JVM และ CLR จริงสามารถมั่นใจได้เท่าที่ฉันรู้ ฉันไม่รู้ว่ามันทำมาตลอด แต่คุณสามารถปกป้องแพคเกจ / แอสเซมบลีต่อการบุกรุกดังกล่าวได้
Konrad Rudolph

27

ตัวอย่างที่เป็นที่ยอมรับคือโอเวอร์โหลดโอเปอเรเตอร์ << การใช้งานทั่วไปอีกอย่างหนึ่งคือการอนุญาตให้ผู้ช่วยเหลือหรือผู้ดูแลระบบสามารถเข้าถึง internals ของคุณได้

ต่อไปนี้เป็นแนวทางบางประการที่ฉันได้ยินเกี่ยวกับเพื่อน C ++ อันสุดท้ายเป็นที่น่าจดจำอย่างยิ่ง

  • เพื่อนของคุณไม่ใช่เพื่อนของลูก
  • เพื่อนของลูกของคุณไม่ใช่เพื่อนของคุณ
  • มีเพียงเพื่อนเท่านั้นที่สามารถสัมผัสชิ้นส่วนส่วนตัวของคุณได้

" ตัวอย่างที่บัญญัติไว้คือการใช้งานเกินพิกัด <<. " บัญญัติของการไม่ใช้friendฉันเดา
curiousguy

16

แก้ไข: การอ่านคำถามต่อไปอีกหน่อยฉันชอบความคิดของผู้ประกอบการ << >> มากไปและเพิ่มเป็นเพื่อนของชั้นเรียนเหล่านั้น แต่ฉันไม่แน่ใจว่าสิ่งนี้จะไม่ทำลาย encapsulation หรือไม่

มันจะทำลาย encapsulation ได้อย่างไร?

คุณทำลาย encapsulation เมื่อคุณอนุญาตการเข้าถึงสมาชิกข้อมูลที่ไม่ จำกัด พิจารณาคลาสต่อไปนี้:

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ไม่ทำลาย encapsulation ทำให้โปรแกรมเมอร์ Java บางคนรู้สึกอึดอัดเพราะเมื่อพวกเขาพูดว่า "OOP" พวกเขาหมายถึง "Java" เมื่อพวกเขาพูดว่า "Encapsulation" พวกเขาไม่ได้หมายความว่า "สมาชิกส่วนตัวต้องได้รับการปกป้องจากการเข้าถึงโดยพลการ" แต่ "คลาส Java ที่ฟังก์ชันเดียวที่สามารถเข้าถึงสมาชิกส่วนตัวเป็นสมาชิกคลาส" ถึงแม้ว่าเรื่องนี้จะไร้สาระสมบูรณ์สำหรับ ด้วยเหตุผลหลายประการ

ก่อนอื่นดังที่แสดงไว้แล้วมัน จำกัด เกินไป ไม่มีเหตุผลใดที่วิธีการเพื่อนไม่ควรได้รับอนุญาตให้ทำเช่นเดียวกัน

ประการที่สองมันเป็นไปไม่เข้มงวดพอ พิจารณาชั้นสี่:

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

สิ่งนี้ตามความคิดของ Java ที่กล่าวมาแล้วนั้นถูกห่อหุ้มอย่างสมบูรณ์แบบ และยังอนุญาตให้ทุกคนอ่านและแก้ไข xได้ มันทำให้รู้สึกยังไง? (คำใบ้: มันไม่ได้)

Bottom line: Encapsulation นั้นเกี่ยวกับความสามารถในการควบคุมว่าฟังก์ชันใดที่สามารถเข้าถึงสมาชิกส่วนตัว มันไม่เกี่ยวกับที่นิยามของฟังก์ชันเหล่านี้อย่างแม่นยำ


10

อีกตัวอย่างทั่วไปของแอนดรูว์ตัวอย่างรหัสโคลงสั้น ๆ

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);
}

กล่าวอีกนัยหนึ่งคุณสามารถทำให้อินเทอร์เฟซสาธารณะเล็กลงและบังคับใช้ค่าคงที่ที่ตัดผ่านคลาสและวัตถุในฟังก์ชันเพื่อน


6
ทำไมทุกคนต้องการเพื่อนสำหรับสิ่งนั้น ทำไมไม่ให้addChildฟังก์ชันสมาชิกตั้งค่าแม่ด้วย
นาวาซ

1
ตัวอย่างที่ดีกว่าคือการสร้างsetParentเพื่อนเนื่องจากคุณไม่ต้องการให้ลูกค้าเปลี่ยนผู้ปกครองเนื่องจากคุณจะจัดการมันในหมวดหมู่addChild/ removeChildฟังก์ชั่น
Ylisar

8

คุณควบคุมสิทธิ์การเข้าถึงสำหรับสมาชิกและฟังก์ชั่นโดยใช้ส่วนตัว / ได้รับการป้องกัน / สาธารณะใช่ไหม? ดังนั้นสมมติว่าความคิดของแต่ละคนและทุก ๆ 3 ระดับนั้นชัดเจนแล้วมันควรจะชัดเจนว่าเราขาดบางสิ่งบางอย่าง ...

ตัวอย่างเช่นการประกาศของสมาชิก / ฟังก์ชั่นที่ได้รับการป้องกันนั้นค่อนข้างทั่วไป คุณกำลังบอกว่าฟังก์ชั่นนี้ไม่สามารถเข้าถึงได้สำหรับทุกคน (ยกเว้นเด็กที่สืบทอดมาแน่นอน) แต่จะมีข้อยกเว้นอะไรบ้าง ระบบรักษาความปลอดภัยทุกระบบช่วยให้คุณมี 'รายการขาว' บางประเภทใช่มั้ย

ดังนั้นเพื่อนช่วยให้คุณมีความยืดหยุ่นในการแยกวัตถุหินแข็ง แต่อนุญาตให้สร้าง "ช่องโหว่" สำหรับสิ่งที่คุณรู้สึกว่าเป็นธรรม

ฉันเดาว่าผู้คนบอกว่าไม่จำเป็นเพราะมีการออกแบบที่จะทำโดยไม่มีมันอยู่เสมอ ฉันคิดว่ามันคล้ายกับการอภิปรายของตัวแปรทั่วโลก: คุณไม่ควรใช้มันมีวิธีทำโดยไม่มีพวกเขาอยู่เสมอ ... แต่ในความเป็นจริงคุณจะเห็นกรณีที่กลายเป็นวิธีที่สง่างามที่สุด (เกือบ) .. ฉันคิดว่านี่เป็นกรณีเดียวกันกับเพื่อน

มันไม่ได้ดีอะไรเลยนอกจากให้คุณเข้าถึงตัวแปรสมาชิกโดยไม่ต้องใช้ฟังก์ชั่นการตั้งค่า

ดีที่ไม่ตรงวิธีดู ความคิดคือการควบคุม WHO สามารถเข้าถึงสิ่งที่มีหรือไม่ฟังก์ชั่นการตั้งค่ามีน้อยจะทำอย่างไรกับมัน


2
เป็นวิธีการfriendหนี? ช่วยให้วิธีการที่ระบุไว้ในชั้นเรียนเข้าถึงสมาชิกส่วนตัวของมัน มันยังคงไม่ยอมให้มีการเข้าถึงโค้ดโดยพลการ เช่นนี้ไม่แตกต่างจากฟังก์ชั่นสมาชิกสาธารณะ
jalf

เพื่อนสนิทที่สุดเท่าที่คุณจะไปถึงการเข้าถึงระดับแพ็คเกจ C # / Java ใน C ++ @jalf - แล้วคลาสเพื่อนล่ะ (เช่นคลาสโรงงาน)
Ogre Psalm33

1
@Ogre: เกี่ยวกับพวกเขา? คุณยังคงให้ชั้นเรียนนั้นโดยเฉพาะและไม่มีใครสามารถเข้าถึงภายในของชั้นเรียนได้ คุณไม่เพียงแค่เปิดประตูทิ้งไว้เพราะรหัสที่ไม่รู้จักจะทำให้เข้ากับคลาสของคุณ
jalf

8

ฉันพบสถานที่ที่สะดวกในการใช้การเข้าถึงของเพื่อน: ไม่ใช้งานส่วนตัวที่สุด


แต่ฟังก์ชั่นสาธารณะสามารถใช้กับสิ่งนั้นได้หรือ ข้อดีของการใช้การเข้าถึงแบบเพื่อนคืออะไร
Zheng Qu

@Maverobot คุณช่วยอธิบายคำถามของคุณได้ไหม?
VladimirS

5

เพื่อนมามีประโยชน์เมื่อคุณกำลังสร้างตู้คอนเทนเนอร์และคุณต้องการใช้ตัววนซ้ำสำหรับคลาสนั้น


4

เรามีปัญหาที่น่าสนใจเกิดขึ้นกับ บริษัท ที่ก่อนหน้านี้ฉันเคยทำงานที่เราใช้เพื่อนเป็นคนดี ฉันทำงานในแผนกเฟรมเวิร์กของเราเราได้สร้างระบบระดับเอ็นจิ้นพื้นฐานเหนือระบบปฏิบัติการที่กำหนดเองของเรา ภายในเรามีโครงสร้างชั้นเรียน:

         Game
        /    \
 TwoPlayer  SinglePlayer

คลาสทั้งหมดเหล่านี้เป็นส่วนหนึ่งของกรอบงานและดูแลโดยทีมงานของเรา เกมที่ผลิตโดย บริษัท ถูกสร้างขึ้นจากเฟรมเวิร์กนี้มาจากเกมของเด็ก ๆ ปัญหาคือเกมมีส่วนต่อประสานกับสิ่งต่าง ๆ ที่ SinglePlayer และ TwoPlayer จำเป็นต้องเข้าถึง แต่เราไม่ต้องการให้มีการเปิดเผยนอกคลาสเฟรมเวิร์ก วิธีแก้ปัญหาคือทำให้อินเตอร์เฟสเหล่านั้นเป็นส่วนตัวและอนุญาตให้ TwoPlayer และ SinglePlayer เข้าถึงพวกเขาผ่านมิตรภาพ

ความจริงปัญหาทั้งหมดนี้สามารถแก้ไขได้ด้วยการนำระบบของเราไปใช้ให้ดีขึ้น แต่เราถูกล็อคไว้กับสิ่งที่เรามี


4

คำตอบสั้น ๆ คือ: ใช้เพื่อนเมื่อมันปรับปรุงขึ้นจริงห่อหุ้ม การปรับปรุงความสามารถในการอ่านและการใช้งาน (ตัวดำเนินการ << และ >> เป็นตัวอย่างที่ยอมรับได้) ก็เป็นเหตุผลที่ดีเช่นกัน

สำหรับตัวอย่างของการปรับปรุงการห่อหุ้มคลาสที่ออกแบบมาเป็นพิเศษเพื่อทำงานกับ internals ของคลาสอื่น


" ผู้ประกอบการและ << >> เป็นตัวอย่างที่เป็นที่ยอมรับ " ฉบับที่ค่อนข้างเป็นที่ยอมรับตัวอย่างเคาน์เตอร์
curiousguy

@curtguy: โอเปอเรเตอร์<<และ>>มักจะเป็นเพื่อนแทนที่จะเป็นสมาชิกเพราะการทำให้พวกเขาเป็นสมาชิกจะทำให้พวกเขาอึดอัดใจที่จะใช้ แน่นอนฉันกำลังพูดถึงกรณีที่ผู้ประกอบการเหล่านั้นจำเป็นต้องเข้าถึงข้อมูลส่วนตัว มิฉะนั้นมิตรภาพนั้นไร้ประโยชน์
Gorpik

" เพราะการทำให้พวกเขาเป็นสมาชิกจะทำให้พวกเขาอึดอัดใจในการใช้ " เห็นได้ชัดว่าการสร้างoperator<<และoperator>>สมาชิกของคลาสที่มีค่าแทนที่จะเป็นสมาชิกหรือสมาชิกของi|ostreamจะไม่ให้ไวยากรณ์ที่ต้องการและฉันไม่ได้แนะนำมัน " ฉันกำลังพูดถึงกรณีที่ตัวดำเนินการเหล่านั้นจำเป็นต้องเข้าถึงข้อมูลส่วนตัว " ฉันไม่ค่อยเห็นว่าทำไมตัวดำเนินการอินพุต / เอาท์พุตจึงจำเป็นต้องเข้าถึงสมาชิกส่วนตัว
curiousguy

4

ผู้สร้าง C ++ บอกว่าไม่ได้รับหลักการการห่อหุ้มใด ๆ และฉันจะพูดถึงเขา:

"เพื่อน" ละเมิดการห่อหุ้มหรือไม่? ไม่มันไม่ใช่ "เพื่อน" เป็นกลไกที่ชัดเจนในการอนุญาตการเข้าถึงเช่นเดียวกับการเป็นสมาชิก คุณไม่สามารถ (ในโปรแกรมที่สอดคล้องกับมาตรฐาน) ให้สิทธิ์การเข้าถึงคลาสโดยไม่ต้องดัดแปลงแหล่งที่มา

ชัดเจนยิ่งกว่า ...


@currguy: แม้ในกรณีที่เป็นแม่แบบมันเป็นความจริง
นาวาซ

@Nawaz อาจได้รับมิตรภาพของเทมเพลต แต่ใคร ๆ ก็สามารถสร้างความเชี่ยวชาญบางส่วนหรือชัดเจนโดยไม่ต้องปรับเปลี่ยนคลาสการให้มิตรภาพ แต่ระวังการละเมิด ODR เมื่อคุณทำเช่นนั้น และอย่าทำเช่นนั้นต่อไป
2012

3

การใช้งานอื่น: เพื่อน (+ เสมือนการสืบทอด) สามารถใช้เพื่อหลีกเลี่ยงการได้มาจากคลาส (aka: "Make class underivable") => 1 , 2

จาก2 :

 class Fred;

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

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

3

หากต้องการทำ TDD หลายครั้งฉันได้ใช้คำหลัก 'เพื่อน' ใน C ++

เพื่อนสามารถรู้ทุกอย่างเกี่ยวกับฉันได้ไหม


อัปเดต: ฉันพบคำตอบที่มีคุณค่าเกี่ยวกับ "เพื่อน" คำหลักจากเว็บไซต์ Bjarne Stroustrup

"เพื่อน" เป็นกลไกที่ชัดเจนในการอนุญาตการเข้าถึงเช่นเดียวกับการเป็นสมาชิก


3

คุณต้องระมัดระวังอย่างมากเกี่ยวกับเวลา / สถานที่ที่คุณใช้friendคำหลักและเช่นคุณฉันได้ใช้มันบ่อยมาก ด้านล่างนี้เป็นหมายเหตุเกี่ยวกับการใช้friendและทางเลือก

สมมติว่าคุณต้องการเปรียบเทียบวัตถุสองชิ้นเพื่อดูว่ามันเท่ากันหรือไม่ คุณสามารถ:

  • ใช้วิธีการเข้าถึงเพื่อทำการเปรียบเทียบ (ตรวจสอบทุก ivar และกำหนดความเท่าเทียมกัน)
  • หรือคุณสามารถเข้าถึงสมาชิกทั้งหมดได้โดยตรงโดยทำให้พวกเขาเป็นสาธารณะ

ปัญหาเกี่ยวกับตัวเลือกแรกคือนั่นอาจเป็น accessor จำนวนมากซึ่งช้ากว่าการเข้าถึงตัวแปรโดยตรง (อ่าน) และอ่านยากกว่าเล็กน้อย (เล็กน้อย) ปัญหาเกี่ยวกับวิธีการที่สองคือการที่คุณทำลาย encapsulation อย่างสมบูรณ์

สิ่งที่จะดีคือถ้าเราสามารถกำหนดฟังก์ชั่นภายนอกซึ่งยังคงสามารถเข้าถึงสมาชิกส่วนตัวของชั้นเรียน เราสามารถทำได้ด้วยfriendคำสำคัญ:

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

วิธีการequal(Beer, Beer)ในขณะนี้มีการเข้าถึงโดยตรงaและbส่วนตัวของสมาชิก s (ซึ่งอาจจะchar *brand, float percentAlcoholฯลฯ นี้เป็นตัวอย่าง contrived ค่อนข้างคุณจะเร็วใช้friendไปยังมากเกินไป== operatorแต่เราจะไปที่

สิ่งที่ควรทราบ:

  • A friendไม่ใช่ฟังก์ชันสมาชิกของคลาส
  • มันเป็นฟังก์ชั่นธรรมดาที่มีการเข้าถึงพิเศษให้กับสมาชิกส่วนตัวของชั้นเรียน
  • อย่าแทนที่ accessors และ mutators ทั้งหมดเป็นเพื่อน (คุณอาจทำทุกอย่างได้public!)
  • มิตรภาพไม่ได้ซึ่งกันและกัน
  • มิตรภาพไม่ใช่สกรรมกริยา
  • มิตรภาพไม่ได้รับการสืบทอด
  • หรือตามที่C + คำถามที่พบบ่อยอธิบาย : "เพียงเพราะฉันอนุญาตให้คุณเข้าถึงมิตรภาพกับฉันไม่อนุญาตให้ลูก ๆ ของคุณเข้าถึงฉันโดยอัตโนมัติไม่อนุญาตให้เพื่อนของคุณเข้าถึงฉันโดยอัตโนมัติและไม่อนุญาตให้ฉันเข้าถึงคุณโดยอัตโนมัติ ."

ฉันใช้งานfriendsได้ก็ต่อเมื่อทำได้ยากกว่าเท่านั้น เป็นอีกหนึ่งตัวอย่างฟังก์ชั่นหลายเวกเตอร์คณิตศาสตร์มักจะถูกสร้างขึ้นเป็นfriendsเนื่องจากการทำงานร่วมกันของMat2x2, Mat3x3, Mat4x4, Vec2, Vec3, Vec4ฯลฯ และมันก็เป็นเพียงเพื่อให้ง่ายมากที่จะเป็นเพื่อนกันมากกว่าต้องใช้ accessors ทุกที่ ตามที่ระบุไว้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บ่อยนัก แต่ทุกครั้งแล้วมันก็เป็นสิ่งที่คุณต้องการ หวังว่านี่จะช่วยได้!


2

เกี่ยวกับโอเปอเรเตอร์ << และโอเปอเรเตอร์ >> ไม่มีเหตุผลที่ดีที่จะทำให้ผู้ประกอบการเหล่านี้เป็นเพื่อน เป็นความจริงที่ว่าพวกเขาไม่ควรทำหน้าที่สมาชิก แต่พวกเขาไม่จำเป็นต้องเป็นเพื่อนด้วย

สิ่งที่ดีที่สุดที่จะทำคือสร้างฟังก์ชั่นการพิมพ์แบบสาธารณะ (ostream &) และฟังก์ชั่น read (istream &) จากนั้นเขียนโอเปอเรเตอร์ << และโอเปอเรเตอร์ >> ในแง่ของฟังก์ชั่นเหล่านั้น สิ่งนี้ให้ประโยชน์เพิ่มเติมของการอนุญาตให้คุณทำให้ฟังก์ชันเหล่านั้นเสมือนจริงซึ่งมีการจัดลำดับเสมือนจริง


" เกี่ยวกับโอเปอเรเตอร์ << และโอเปอเรเตอร์ >> ไม่มีเหตุผลที่ดีที่จะทำให้ผู้ประกอบการเหล่านี้เป็นเพื่อนกัน " ถูกต้องแน่นอน " สิ่งนี้ให้ประโยชน์เพิ่มเติมจากการอนุญาตให้คุณทำให้ฟังก์ชั่นเหล่านั้นเสมือน " ถ้าคลาสที่มีปัญหานั้นมีจุดประสงค์เพื่อให้ได้มา มิฉะนั้นจะรำคาญทำไม
curiousguy

ฉันไม่เข้าใจว่าทำไมคำตอบนี้จึงลดลงสองครั้ง - และไม่มีคำอธิบายเลย! นั่นหยาบคาย
curiousguy

เสมือนจะเพิ่มความนิยมอย่างสมบูรณ์ซึ่งอาจมีขนาดค่อนข้างใหญ่ในการจัดลำดับ
paulm

2

ฉันแค่ใช้คำหลักเพื่อนเพื่อฟังก์ชั่นการป้องกัน unittest บางคนจะบอกว่าคุณไม่ควรทดสอบการทำงานที่ได้รับการป้องกัน อย่างไรก็ตามฉันพบว่าเครื่องมือนี้มีประโยชน์มากเมื่อเพิ่มฟังก์ชันการทำงานใหม่

อย่างไรก็ตามฉันไม่ได้ใช้คำหลักในการประกาศคลาสโดยตรง แต่ฉันใช้เทมเพลตแฮ็กที่ดีเพื่อให้ได้ผลลัพธ์นี้:

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();

ทำงานบน GCC และ MSVC อย่างน้อย


2

ในคำหลัก "เพื่อน" C ++ มีประโยชน์ในการใช้งานมากเกินไปและสร้างสะพาน

1. ) คำหลักเพื่อนในการใช้งานการบรรทุกเกินพิกัด:
ตัวอย่างสำหรับการบรรทุกเกินพิกัดคือ: สมมติว่าเรามีคลาส "จุด" ที่มีตัวแปรลอยสอง
"x" (สำหรับพิกัด x) และ "y" (สำหรับพิกัด y) ตอนนี้เราต้องโอเวอร์โหลด"<<"(โอเปอเรเตอร์ดึงข้อมูล) ดังนั้นถ้าเราเรียก"cout << pointobj"มันจะพิมพ์พิกัด x และ y (โดยที่ pointobj เป็นวัตถุของคลาสพอยต์) ในการทำสิ่งนี้เรามีสองตัวเลือก:

   1. โอเวอร์โหลด "โอเปอเรเตอร์ << ()" ฟังก์ชั่นในคลาส "ostream"
   2. โอเวอร์โหลด "โอเปอเรเตอร์ << ()" ฟังก์ชั่นในคลาส "พอยต์"
ตอนนี้ตัวเลือกแรกไม่ดีเพราะถ้าเราต้องการโอเวอร์โหลดอีกครั้งตัวดำเนินการนี้สำหรับคลาสที่แตกต่างกันดังนั้นเราต้องทำการเปลี่ยนแปลงในคลาส "ostream" อีกครั้ง
นั่นเป็นเหตุผลที่สองเป็นตัวเลือกที่ดีที่สุด ตอนนี้คอมไพเลอร์สามารถเรียก "operator <<()"ใช้ฟังก์ชัน:

   1. ใช้ cout. วัตถุ ostream เป็น: cout.operator << (Pointobj) (ฟอร์มคลาส ostream) 
2. โทรโดยไม่มีวัตถุในขณะที่: โอเปอเรเตอร์ << (cout, Pointobj) (จากคลาสพอยต์)

เพราะเรามีการใช้งานมากไปในคลาสพอยน์ ดังนั้นการเรียกใช้ฟังก์ชั่นนี้โดยไม่มีวัตถุเราต้องเพิ่ม"friend"คีย์เวิร์ดเพราะเราสามารถเรียกใช้ฟังก์ชันเพื่อนที่ไม่มีวัตถุได้ ตอนนี้ฟังก์ชั่นการประกาศจะเป็น:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"

2. ) คำหลักเพื่อนในการสร้างสะพาน:
สมมติว่าเราต้องทำฟังก์ชั่นที่เราต้องเข้าถึงสมาชิกส่วนตัวของสองคนขึ้นไป (เรียกว่า "สะพาน") วิธีการทำเช่นนี้:
ในการเข้าถึงสมาชิกส่วนตัวของชั้นเรียนควรเป็นสมาชิกของชั้นเรียนนั้น ตอนนี้การเข้าถึงสมาชิกส่วนตัวของคลาสอื่นทุกคลาสควรประกาศฟังก์ชันนั้นเป็นฟังก์ชันเพื่อน ตัวอย่างเช่นสมมติว่ามีสองคลาส A และ B ฟังก์ชันที่"funcBridge()"ต้องการเข้าถึงสมาชิกส่วนตัวของทั้งสองคลาส จากนั้นทั้งสองชั้นควรประกาศ"funcBridge()"ว่า:
friend return_type funcBridge(A &a_obj, B & b_obj);

ฉันคิดว่านี่จะช่วยให้เข้าใจคำหลักของเพื่อนได้


2

ในฐานะที่เป็นข้อมูลอ้างอิงสำหรับการประกาศเพื่อนบอกว่า:

การประกาศของเพื่อนจะปรากฏในเนื้อหาของคลาสและให้สิทธิ์การใช้งานหรือการเข้าถึงคลาสอื่นให้กับสมาชิกส่วนตัวและได้รับการป้องกันของชั้นเรียนที่การประกาศของเพื่อนปรากฏขึ้น

ดังนั้นเพื่อเป็นการเตือนความจำมีข้อผิดพลาดทางเทคนิคในคำตอบบางอย่างที่บอกว่าfriendสามารถเข้าเยี่ยมสมาชิกที่ได้รับความคุ้มครองเท่านั้น


1

ตัวอย่างต้นไม้เป็นตัวอย่างที่ดีงาม: มีวัตถุที่นำมาใช้ในชั้นเรียนที่แตกต่างกันไม่กี่โดยไม่ต้องมีความสัมพันธ์ที่สืบทอด

บางทีคุณอาจต้องการให้มันมีคอนสตรัคเตอร์ที่ได้รับการปกป้องและบังคับให้คนอื่นใช้โรงงาน "เพื่อน" ของคุณ

... โอเคตรงไปตรงมาคุณสามารถอยู่ได้โดยปราศจากมัน


1

หากต้องการทำ TDD หลายครั้งฉันได้ใช้คำหลัก 'เพื่อน' ใน C ++
เพื่อนสามารถรู้ทุกอย่างเกี่ยวกับฉันได้ไหม

ไม่มันเป็นเพียงมิตรภาพทางเดียว: `(


1

อินสแตนซ์ที่เฉพาะเจาะจงอย่างหนึ่งที่ฉันใช้friendคือเมื่อสร้างคลาสSingleton 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;
}

นี่อาจเป็นเรื่องของรสนิยม แต่ฉันไม่คิดว่าการบันทึกการกดแป้นบางครั้งจะพิสูจน์ให้เห็นถึงการใช้งานของเพื่อนที่นี่ GetMySingleton () ควรเป็นวิธีการคงที่ของคลาส
Gorpik

c-tor ส่วนตัวจะไม่อนุญาตให้ฟังก์ชั่นที่ไม่ใช่เพื่อนยกตัวอย่าง MySingleton ดังนั้นจำเป็นต้องใช้คำหลักเพื่อนที่นี่
JBRWilkinson

@Gorpik " นี่อาจเป็นเรื่องของการลิ้มรส แต่ฉันไม่คิดว่าการบันทึกการกดแป้นบางครั้งจะพิสูจน์ให้เห็นถึงการใช้งานของเพื่อนที่นี่ " อย่างไรก็ตามfriendไม่ไม่จำเป็นต้องมีโดยเฉพาะอย่างยิ่ง "เหตุผล" เมื่อมีการเพิ่มฟังก์ชั่นสมาชิกไม่ได้
curiousguy

Singletons นั้นถือว่าเป็นการปฏิบัติที่ไม่ดีอยู่ดี (Google "singleton เป็นอันตราย" และคุณจะได้รับผลลัพธ์มากมายเช่นนี้ฉันไม่คิดว่าการใช้คุณลักษณะเพื่อใช้ antipattern นั้นถือได้ว่าเป็นการใช้ประโยชน์จากคุณลักษณะนั้นดี
weberc2

1

ฟังก์ชั่นเพื่อนและชั้นเรียนให้การเข้าถึงโดยตรงไปยังสมาชิกส่วนตัวและป้องกันของชั้นเรียนเพื่อหลีกเลี่ยงการทำลายในกรณีทั่วไป การใช้งานส่วนใหญ่อยู่กับ ostream: เราต้องการที่จะพิมพ์:

Point p;
cout << p;

อย่างไรก็ตามสิ่งนี้อาจต้องมีการเข้าถึงข้อมูลส่วนตัวของ Point ดังนั้นเราจึงกำหนดโอเปอเรเตอร์ที่โอเวอร์โหลด

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 คอมไพเลอร์จะปรับการเรียกใช้ฟังก์ชันให้เหมาะสมดังนั้นสิ่งนี้จะส่งผลเหมือนกัน คำแนะนำเป็นการเข้าถึงโดยตรง

ฉันคิดว่าการใช้ 'เพื่อน' เป็นเพียงทางลัดที่มีประโยชน์ที่พิสูจน์ได้ แต่มีค่าใช้จ่ายที่แน่นอน


1

นี่อาจไม่ใช่สถานการณ์กรณีการใช้งานจริง แต่อาจช่วยแสดงให้เห็นถึงการใช้เพื่อนระหว่างชั้นเรียน

The ClubHouse

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 มีสมาชิกหลากหลายประเภทและการเข้าเป็นสมาชิก สมาชิกทั้งหมดได้รับมาจากคลาส super หรือคลาสพื้นฐานเนื่องจากพวกเขาทั้งหมดแชร์ ID และชนิดที่แจกแจงซึ่งเป็นคลาสทั่วไปและภายนอกสามารถเข้าถึง ID และชนิดของพวกเขาผ่านฟังก์ชันการเข้าถึงที่พบในคลาสพื้นฐาน

อย่างไรก็ตามผ่านลำดับชั้นของสมาชิกและคลาส Derived และความสัมพันธ์กับคลาส ClubHouse เพียงคลาสเดียวเท่านั้นที่ได้รับ "สิทธิพิเศษ" คือคลาส VIPMember คลาสพื้นฐานและคลาสอื่น ๆ ที่ได้รับ 2 คลาสไม่สามารถเข้าถึงวิธี joinVIPEvent () ของ ClubHouse แต่คลาสสมาชิก VIP มีสิทธิ์นั้นราวกับว่ามีการเข้าถึงเหตุการณ์นั้นอย่างสมบูรณ์

ดังนั้นด้วย VIPMember และ ClubHouse จึงเป็นทางเข้าสองทางที่สมาชิกคลาสอื่นจะถูก จำกัด


0

เมื่อใช้อัลกอริธึมแบบต้นไม้สำหรับคลาสรหัสเฟรมเวิร์กที่ให้ไว้กับเราจะมีคลาสของต้นไม้เป็นเพื่อนของคลาสโหนด

มันไม่ได้ดีอะไรเลยนอกจากให้คุณเข้าถึงตัวแปรสมาชิกโดยไม่ต้องใช้ฟังก์ชั่นการตั้งค่า


0

คุณอาจใช้มิตรภาพเมื่อชั้นเรียนที่แตกต่างกัน (ไม่ได้สืบทอดจากอีกอันหนึ่ง) กำลังใช้งานส่วนตัวหรือสมาชิกที่ได้รับความคุ้มครองในชั้นเรียนอื่น

กรณีการใช้งานโดยทั่วไปของฟังก์ชั่นเพื่อนเป็นการดำเนินการที่ดำเนินการระหว่างสองคลาสที่แตกต่างกันเพื่อเข้าถึงสมาชิกส่วนตัวหรือสมาชิกที่ได้รับความคุ้มครองของทั้งสอง

จากhttp://www.cplusplus.com/doc/tutorial/inheritance/ 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;
}

0

อาจเป็นเพราะฉันพลาดบางสิ่งจากคำตอบข้างต้น แต่แนวคิดสำคัญอีกประการหนึ่งในการห่อหุ้มคือการซ่อนการใช้งาน การลดการเข้าถึงข้อมูลส่วนตัวของสมาชิก (รายละเอียดการใช้งานของชั้นเรียน) ช่วยให้แก้ไขรหัสได้ง่ายขึ้นในภายหลัง หากเพื่อนเข้าถึงข้อมูลส่วนตัวโดยตรงการเปลี่ยนแปลงใด ๆ ในฟิลด์ข้อมูลการนำไปใช้ (ข้อมูลส่วนตัว) ให้แบ่งรหัสที่เข้าถึงข้อมูลนั้น การใช้วิธีการเข้าถึงส่วนใหญ่จะกำจัดสิ่งนี้ ฉันคิดว่าสำคัญพอสมควร


-1

คุณสามารถเป็นไปตามหลักการที่เข้มงวดและบริสุทธิ์ OOP และให้แน่ใจว่าไม่มีข้อมูลสมาชิกสำหรับการเรียนใด ๆ ได้มีaccessorsเพื่อให้วัตถุทั้งหมดจะต้องเป็นคนเดียวที่สามารถรู้เกี่ยวกับข้อมูลของพวกเขาด้วยวิธีเดียวที่จะดำเนินการกับพวกเขาผ่านทางอ้อมข้อความ , เช่นวิธีการ

แต่แม้แต่ C # ก็มีคีย์เวิร์ดการมองเห็นภายในและ Java มีการเข้าถึงระดับแพ็คเกจเริ่มต้นสำหรับบางสิ่ง C ++ มาใกล้กับอุดมคติ OOP จริง ๆ โดย minimizinbg ประนีประนอมของการมองเห็นในชั้นเรียนโดยการระบุว่าชั้นอื่น ๆ และชั้นอื่น ๆเท่านั้นที่สามารถมองเห็นมัน

ฉันไม่ได้ใช้ C ++ จริงๆ แต่ถ้า C # มีเพื่อนฉันจะทำเช่นนั้นแทนที่จะเป็นตัวดัดแปลงภายในชุดประกอบทั่วโลกซึ่งฉันใช้บ่อยมาก มันไม่ได้ทำลายความแคปมากเพราะหน่วยการปรับใช้ใน. NET เป็นแอสเซมบลี

แต่แล้วก็มีInternalsVisibleTo Attribute (otherAssembly) ซึ่งทำหน้าที่เหมือนกลไกเพื่อนข้ามการชุมนุม Microsoft ใช้สิ่งนี้สำหรับชุดประกอบการออกแบบภาพ


-1

เพื่อนก็มีประโยชน์สำหรับการโทรกลับ คุณสามารถใช้การเรียกกลับเป็นวิธีการคงที่

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 และ friended สิ่งนี้ยอมให้ coder ซ่อนความซับซ้อนและการทำงานภายในของคลาสจากผู้ใช้ส่วนหัว

ในส่วนหัว:

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

ใน cpp

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

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

มันง่ายกว่าที่จะซ่อนสิ่งที่ปลายน้ำไม่ต้องการเห็นด้วยวิธีนี้


1
อินเทอร์เฟซจะไม่เป็นวิธีที่สะอาดกว่าเพื่อบรรลุเป้าหมายนี้หรือไม่ อะไรจะหยุดคนที่ค้นหา MyFooPrivate.h
JBRWilkinson

1
ถ้าคุณใช้ความเป็นส่วนตัวและสาธารณะเพื่อเก็บความลับคุณจะพ่ายแพ้อย่างง่ายดาย เมื่อ "ซ่อน" ฉันหมายถึงผู้ใช้ MyFoo ไม่จำเป็นต้องเห็นสมาชิกส่วนตัวจริงๆ นอกจากนี้ยังมีประโยชน์ในการรักษาความเข้ากันได้ของ ABI หากคุณสร้าง _private พอยน์เตอร์การใช้งานส่วนตัวสามารถเปลี่ยนแปลงได้มากเท่าที่คุณต้องการโดยไม่ต้องสัมผัสกับส่วนต่อประสานสาธารณะดังนั้นจึงรักษาความเข้ากันได้ของ ABI
shash

คุณกำลังอ้างถึงสำนวน PIMPL; จุดประสงค์ที่ไม่ใช่การห่อหุ้มเพิ่มเติมอย่างที่คุณพูด แต่เพื่อย้ายรายละเอียดการใช้งานออกจากส่วนหัวดังนั้นการเปลี่ยนรายละเอียดการใช้งานจะไม่บังคับให้คอมไพล์รหัสลูกค้าซ้ำ นอกจากนี้ไม่จำเป็นต้องใช้เพื่อนในการใช้สำนวนนี้
weberc2

ก็ใช่ วัตถุประสงค์หลักคือเพื่อย้ายรายละเอียดการใช้งาน เพื่อนมีประโยชน์ในการจัดการสมาชิกส่วนตัวในชั้นเรียนสาธารณะจากส่วนตัวหรือวิธีอื่น ๆ
shash
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.