Khi nào bạn nên sử dụng 'bạn bè' trong C ++?


354

Tôi đã đọc qua C ++ FAQ và tò mò về friendtuyên bố. Cá nhân tôi chưa bao giờ sử dụng nó, tuy nhiên tôi thích khám phá ngôn ngữ.

Một ví dụ tốt về việc sử dụng là friendgì?


Đọc FAQ lâu hơn một chút tôi thích ý tưởng về << >>toán tử nạp chồng và thêm vào như một người bạn của các lớp đó. Tuy nhiên tôi không chắc làm thế nào điều này không phá vỡ đóng gói. Khi nào những ngoại lệ này có thể nằm trong sự nghiêm ngặt đó là OOP?


5
Mặc dù tôi đồng ý với câu trả lời rằng một lớp bạn bè không nhất thiết là Điều xấu, tôi có xu hướng coi nó như một mã nhỏ. Nó thường, mặc dù không phải lúc nào cũng chỉ ra rằng hệ thống phân cấp lớp cần xem xét lại.
Mawg nói rằng phục hồi Monica

1
Bạn sẽ sử dụng một lớp bạn bè, nơi đã có khớp nối chặt chẽ. Đó là những gì nó được làm cho. Ví dụ, một bảng cơ sở dữ liệu và các chỉ mục của nó được liên kết chặt chẽ. Khi một bảng thay đổi, tất cả các chỉ mục của nó phải được cập nhật. Vì vậy, lớp DB Index sẽ khai báo DBTable như một người bạn để DBTable có thể truy cập trực tiếp vào bên trong chỉ mục. Nhưng sẽ không có giao diện công khai cho DB Index; thậm chí không có ý nghĩa gì khi đọc một chỉ mục.
Shawnhcorey

OOP "những người theo chủ nghĩa thuần túy" với ít kinh nghiệm thực tế cho rằng bạn bè vi phạm các nguyên tắc OOP, bởi vì một lớp nên là người duy trì trạng thái riêng tư của nó. Điều này là tốt, cho đến khi bạn gặp một tình huống phổ biến trong đó hai lớp cần duy trì trạng thái riêng tư chung.
kaalus

Câu trả lời:


335

Thứ nhất (IMO) không lắng nghe những người nói friendkhông hữu ích. Nó rất hữu ích. Trong nhiều tình huống, bạn sẽ có các đối tượng với dữ liệu hoặc chức năng không có ý định công khai. Điều này đặc biệt đúng với các cơ sở mã lớn với nhiều tác giả chỉ có thể quen thuộc với các lĩnh vực khác nhau.

Có những lựa chọn thay thế cho trình xác định bạn bè, nhưng thường thì chúng cồng kềnh (lớp bê tông cấp cpp / typedefs đeo mặt nạ) hoặc không thể đánh lừa (nhận xét hoặc quy ước tên hàm).

Lên câu trả lời;

Trình friendxác định cho phép lớp được chỉ định truy cập vào dữ liệu hoặc chức năng được bảo vệ trong lớp tạo ra tuyên bố kết bạn. Ví dụ, trong đoạn mã dưới đây, bất kỳ ai cũng có thể hỏi tên của một đứa trẻ, nhưng chỉ có mẹ và đứa trẻ có thể thay đổi tên.

Bạn có thể lấy ví dụ đơn giản này hơn nữa bằng cách xem xét một lớp phức tạp hơn như Cửa sổ. Rất có thể một Cửa sổ sẽ có nhiều yếu tố chức năng / dữ liệu không thể truy cập công khai, nhưng được một lớp liên quan như WindowManager cần.

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
Là một lưu ý thêm, C ++ FAQ đề cập đến việc friend tăng cường đóng gói. friendcấp quyền truy cập có chọn lọc cho các thành viên, giống như protectedkhông. Bất kỳ kiểm soát chi tiết tốt hơn là cấp quyền truy cập công cộng. Các ngôn ngữ khác cũng xác định các cơ chế truy cập chọn lọc, xem xét C # 's internal. Hầu hết những lời chỉ trích tiêu cực xung quanh việc sử dụng friendcó liên quan đến khớp nối chặt chẽ hơn, thường được xem là một điều xấu. Tuy nhiên, trong một số trường hợp, khớp nối chặt chẽ hơn chính xác là những gì bạn muốn và friendcung cấp cho bạn sức mạnh đó.
André Caron

5
Bạn có thể vui lòng nói thêm về (các lớp bê tông cấp cpp) và (typedefs đeo mặt nạ), Andrew ?
OmarOthman

18
Câu trả lời này dường như tập trung hơn vào việc giải thích những gì friendhơn là cung cấp một ví dụ động lực . Ví dụ Window / WindowManager tốt hơn ví dụ hiển thị, nhưng quá mơ hồ. Câu trả lời này cũng không đề cập đến phần đóng gói của câu hỏi.
bames53

4
Vì vậy, "bạn" có hiệu quả vì C ++ không có khái niệm về gói mà tất cả các thành viên có thể chia sẻ chi tiết triển khai? Tôi sẽ thực sự quan tâm đến một ví dụ thực tế.
weberc2

1
Tôi nghĩ rằng ví dụ Mẹ / Con là không may. Các tên này phù hợp với các thể hiện, nhưng không phải cho các lớp. (Vấn đề cho thấy nếu chúng tôi có 2 bà mẹ và mỗi người có con riêng).
Jo So

162

Trong công việc, chúng tôi sử dụng bạn bè để kiểm tra mã , rộng rãi. Nó có nghĩa là chúng tôi có thể cung cấp việc đóng gói thông tin và ẩn thông tin thích hợp cho mã ứng dụng chính. Nhưng chúng ta cũng có thể có mã kiểm tra riêng sử dụng bạn bè để kiểm tra trạng thái nội bộ và dữ liệu để kiểm tra.

Đủ để nói rằng tôi sẽ không sử dụng từ khóa bạn bè như một thành phần thiết yếu trong thiết kế của bạn.


Đó chính xác là những gì tôi sử dụng nó cho. Điều đó hoặc chỉ đặt các biến thành viên để bảo vệ. Thật đáng tiếc, nó không hoạt động đối với C ++ / CLI :-(
Jon Cage

12
Cá nhân tôi sẽ không khuyến khích điều này. Thông thường, bạn đang kiểm tra một giao diện, tức là một bộ đầu vào sẽ cung cấp (các) đầu ra dự kiến. Tại sao bạn cần kiểm tra dữ liệu nội bộ?
Graeme

55
@Graeme: Bởi vì một kế hoạch kiểm tra tốt bao gồm cả kiểm tra hộp trắng và hộp đen.
Ben Voigt

1
Tôi có xu hướng đồng ý với @Graeme, như đã giải thích hoàn hảo trong câu trả lời này .
Alexis Leclerc

2
@Graeme nó có thể không phải là dữ liệu nội bộ trực tiếp. Tôi có thể là một phương thức thực hiện một thao tác hoặc tác vụ cụ thể trên dữ liệu đó trong đó phương thức đó là riêng tư đối với lớp và không nên truy cập công khai trong khi một số đối tượng khác có thể cần phải cung cấp hoặc gieo mầm phương thức được bảo vệ của lớp đó bằng dữ liệu của chính nó.
Francis Cugler

93

Các friendtừ khóa có một số sử dụng tốt. Dưới đây là hai cách sử dụng ngay lập tức với tôi:

Định nghĩa bạn bè

Định nghĩa bạn bè cho phép xác định hàm trong phạm vi lớp, nhưng hàm sẽ không được định nghĩa là hàm thành viên, mà là hàm miễn phí của không gian tên kèm theo và sẽ không hiển thị bình thường trừ khi tra cứu phụ thuộc đối số. Điều đó làm cho nó đặc biệt hữu ích cho quá tải toán tử:

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
}

Lớp cơ sở CRTP riêng

Đôi khi, bạn thấy nhu cầu mà chính sách cần truy cập vào lớp dẫn xuất:

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

Bạn sẽ tìm thấy một ví dụ không giả định cho điều đó trong câu trả lời này . Một mã khác sử dụng đó là trong câu trả lời này . Cơ sở CRTP tạo ra con trỏ của nó, để có thể truy cập các trường dữ liệu của lớp dẫn xuất bằng cách sử dụng các con trỏ thành viên dữ liệu.


Xin chào, tôi gặp lỗi cú pháp (trong xcode 4) khi tôi thử CRTP của bạn. Xcode tin rằng tôi đang cố gắng kế thừa một mẫu lớp. Lỗi này xảy ra ở P<C>trong template<template<typename> class P> class C : P<C> {};đó nêu "Sử dụng lớp mẫu C đòi hỏi lập luận mẫu". Bạn đã có những vấn đề tương tự hoặc có thể biết một giải pháp?
bennedich

@bennedich thoạt nhìn, có vẻ như là loại lỗi bạn gặp phải khi không hỗ trợ tính năng C ++. Đó là khá phổ biến trong số các trình biên dịch. Việc sử dụng FlexibleClassbên trong FlexibleClassnên ngầm đề cập đến loại của chính nó.
Yakk - Adam Nevraumont

@bennedich: Các quy tắc sử dụng tên của mẫu lớp từ bên trong thân lớp đã thay đổi với C ++ 11. Hãy thử bật chế độ C ++ 11 trong trình biên dịch của bạn.
Ben Voigt

Trong Visual Studio 2015 thêm công khai này: f () {}; f (int_type t): giá trị (t) {}; Để ngăn lỗi trình biên dịch này: lỗi C2440: '<function-style-cast>': không thể chuyển đổi từ 'utils :: f :: int_type' sang 'utils :: f' lưu ý: Không có hàm tạo nào có thể lấy loại nguồn hoặc hàm tạo độ phân giải quá tải không rõ ràng
Damian

41

@roo : Đóng gói không bị phá vỡ ở đây vì chính lớp này ra lệnh ai có thể truy cập các thành viên riêng của nó. Đóng gói sẽ chỉ bị phá vỡ nếu điều này có thể được gây ra từ bên ngoài lớp học, ví dụ nếu bạn operator <<sẽ tuyên bố tôi là bạn của lớp foo.

friendthay thế sử dụng public, không sử dụng private!

Trên thực tế, C ++ FAQ đã trả lời câu hỏi này .


14
"Bạn bè thay thế việc sử dụng công cộng, không sử dụng riêng tư!", tôi thứ hai
Waleed Eissa

26
@Assaf: có, nhưng FQA, đối với hầu hết các phần a, rất nhiều tiếng nói giận dữ không liên tục mà không có giá trị thực sự. Phần trên friendcũng không ngoại lệ. Quan sát thực tế duy nhất ở đây là C ++ đảm bảo đóng gói chỉ trong thời gian biên dịch. Và bạn không cần thêm lời nào để nói điều đó. Phần còn lại là bollocks. Vì vậy, tóm lại: phần này của FQA không có gì đáng nói.
Konrad Rudolph

12
Hầu hết FQA đó là hoàn toàn blx :)
rama-jka toti

1
@Konrad: "Quan sát thực sự duy nhất ở đây là C ++ đảm bảo đóng gói chỉ trong thời gian biên dịch." Có ngôn ngữ nào đảm bảo điều này trong thời gian chạy không? Theo như tôi biết, việc trả về các tham chiếu cho các thành viên riêng (và các hàm, đối với các ngôn ngữ cho phép con trỏ tới các hàm hoặc hàm như các đối tượng hạng nhất) được cho phép trong C #, Java, Python và nhiều ngôn ngữ khác.
André Caron

@ André: JVM và CLR thực sự có thể đảm bảo điều này theo như tôi biết. Tôi không biết nếu nó luôn luôn được thực hiện nhưng bạn có thể bị cáo buộc bảo vệ các gói / hội đồng chống lại sự xâm nhập đó (mặc dù bản thân chưa bao giờ thực hiện việc này).
Konrad Rudolph

27

Ví dụ điển hình là quá tải toán tử <<. Một cách sử dụng phổ biến khác là cho phép người trợ giúp hoặc lớp quản trị viên truy cập vào nội bộ của bạn.

Dưới đây là một vài hướng dẫn tôi đã nghe về bạn bè C ++. Điều cuối cùng là đặc biệt đáng nhớ.

  • Bạn bè của bạn không phải là bạn của con bạn.
  • Bạn bè của con bạn không phải là bạn của bạn.
  • Chỉ bạn bè mới có thể chạm vào những phần riêng tư của bạn.

" Ví dụ kinh điển là quá tải toán tử <<. " Quy tắc không sử dụng friendtôi đoán.
tò mò

16

chỉnh sửa: Đọc faq lâu hơn một chút Tôi thích ý tưởng về toán tử << >> nạp chồng và thêm làm bạn của các lớp đó, tuy nhiên tôi không chắc làm thế nào điều này không phá vỡ đóng gói

Làm thế nào nó sẽ phá vỡ đóng gói?

Bạn phá vỡ đóng gói khi bạn cho phép truy cập không hạn chế vào một thành viên dữ liệu. Hãy xem xét các lớp sau:

class c1 {
public:
  int x;
};

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

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

c1được rõ ràng là không được đóng gói. Bất cứ ai cũng có thể đọc và sửa đổi xtrong đó. Chúng tôi không có cách nào để thực thi bất kỳ loại kiểm soát truy cập nào.

c2rõ ràng là gói gọn. Không có quyền truy cập công cộng vào x. Tất cả những gì bạn có thể làm là gọi foohàm, thực hiện một số thao tác có ý nghĩa trên lớp .

c3? Là ít đóng gói? Nó có cho phép truy cập không hạn chế x? Nó có cho phép các chức năng chưa biết truy cập không?

Không. Nó cho phép chính xác một chức năng để truy cập các thành viên riêng của lớp. Cũng giống như c2đã làm. Và cũng giống như c2, một hàm có quyền truy cập không phải là "một số hàm ngẫu nhiên, không xác định", mà là "hàm được liệt kê trong định nghĩa lớp". Giống như c2, chúng ta có thể thấy, chỉ bằng cách nhìn vào các định nghĩa lớp, một danh sách đầy đủ về những người có quyền truy cập.

Vì vậy, làm thế nào chính xác là điều này ít đóng gói? Cùng một lượng mã có quyền truy cập vào các thành viên riêng của lớp. Và tất cả những người có quyền truy cập được liệt kê trong định nghĩa lớp.

friendkhông phá vỡ đóng gói. Nó làm cho một số người lập trình Java cảm thấy không thoải mái, bởi vì khi họ nói "OOP", họ thực sự có nghĩa là "Java". Khi họ nói "Đóng gói", họ không có nghĩa là "các thành viên riêng phải được bảo vệ khỏi các truy cập tùy ý", mà là "một lớp Java trong đó các hàm duy nhất có thể truy cập các thành viên riêng, là các thành viên của lớp", mặc dù điều này hoàn toàn vô nghĩa đối với nhiều lý do .

Đầu tiên, như đã được chỉ ra, nó quá hạn chế. Không có lý do tại sao các phương pháp bạn bè không nên được phép làm như vậy.

Thứ hai, nó không đủ hạn chế . Hãy xem xét một lớp thứ tư:

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

Điều này, theo tâm lý Java đã nói ở trên, được gói gọn một cách hoàn hảo. Tuy nhiên, nó cho phép hoàn toàn bất cứ ai đọc và sửa đổi x . Làm thế nào mà thậm chí có ý nghĩa? (gợi ý: Không)

Điểm mấu chốt: Đóng gói là về khả năng kiểm soát các chức năng có thể truy cập các thành viên tư nhân. Nó không phải là chính xác nơi định nghĩa của các chức năng này được đặt.


10

Một phiên bản phổ biến khác của ví dụ của Andrew, bộ ghép mã đáng sợ

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

Thay vì lo lắng nếu cả hai dòng luôn được thực hiện cùng nhau và theo thứ tự nhất quán, bạn có thể đặt các phương thức riêng tư và có chức năng kết bạn để thực thi tính nhất quán:

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

Nói cách khác, bạn có thể giữ các giao diện công cộng nhỏ hơn và thực thi các bất biến cắt ngang qua các lớp và đối tượng trong các hàm bạn bè.


6
Tại sao mọi người sẽ cần một người bạn cho điều đó? Tại sao không để addChildchức năng thành viên cũng thiết lập cha mẹ?
Nawaz

1
Một ví dụ tốt hơn sẽ là kết setParentbạn, vì bạn không muốn cho phép khách hàng thay đổi cha mẹ vì bạn sẽ quản lý nó trong addChild/ removeChilddanh mục chức năng.
Ylisar

8

Bạn kiểm soát quyền truy cập cho các thành viên và chức năng bằng Quyền riêng tư / Được bảo vệ / Công khai phải không? Vì vậy, giả sử ý tưởng của mỗi một trong ba cấp độ đó là rõ ràng, thì rõ ràng là chúng ta đang thiếu một cái gì đó ...

Việc khai báo một thành viên / chức năng như được bảo vệ chẳng hạn là khá chung chung. Bạn đang nói rằng chức năng này nằm ngoài tầm với của tất cả mọi người ( tất nhiên ngoại trừ một đứa trẻ được thừa kế). Nhưng những gì về ngoại lệ? mọi hệ thống bảo mật đều cho phép bạn có một số loại 'danh sách trắng "phải không?

Vì vậy, người bạn cho phép bạn linh hoạt cách ly vật thể rắn bằng đá, nhưng cho phép tạo ra "lỗ hổng" cho những thứ mà bạn cảm thấy hợp lý.

Tôi đoán mọi người nói rằng nó không cần thiết bởi vì luôn có một thiết kế sẽ làm mà không có nó. Tôi nghĩ nó tương tự như cuộc thảo luận về các biến toàn cục: Bạn không bao giờ nên sử dụng chúng, Luôn có một cách để làm mà không có chúng ... nhưng thực tế, bạn thấy các trường hợp kết thúc là cách (gần như) thanh lịch nhất. .. Tôi nghĩ đây là trường hợp tương tự với bạn bè.

Nó thực sự không làm được gì, ngoài việc cho phép bạn truy cập vào một biến thành viên mà không cần sử dụng chức năng cài đặt

tốt, đó không phải là cách chính xác để xem xét nó. Ý tưởng là để kiểm soát WHO có thể truy cập những gì, có hoặc không có chức năng cài đặt ít liên quan đến nó.


2
Thế nào là friendmột kẽ hở? Nó cho phép các phương thức được liệt kê trong lớp truy cập các thành viên riêng của nó. Nó vẫn không cho phép mã tùy ý truy cập chúng. Như vậy nó không khác gì chức năng thành viên công cộng.
jalf

friend gần như bạn có thể truy cập cấp độ gói C # / Java trong C ++. @jalf - còn lớp bạn bè (chẳng hạn như lớp nhà máy) thì sao?
Ogre Psalm33

1
@Ogre: Thế còn họ? Bạn vẫn đặc biệt cho lớp đó và không ai khác truy cập vào nội bộ của lớp. Bạn không chỉ để cổng mở cho mã không xác định tùy ý để liên kết với lớp của bạn.
jalf

8

Tôi tìm thấy nơi thuận tiện để sử dụng quyền truy cập của bạn bè: Unittest của các chức năng riêng tư.


Nhưng một chức năng công cộng cũng có thể được sử dụng cho điều đó? Lợi thế của việc sử dụng truy cập bạn bè là gì?
Trịnh Qu

@Maverobot Bạn có thể giải thích về câu hỏi của bạn?
VladimirS

5

Friend rất hữu ích khi bạn đang xây dựng một container và bạn muốn triển khai một trình vòng lặp cho lớp đó.


4

Chúng tôi đã có một vấn đề thú vị xuất hiện tại một công ty mà trước đây tôi làm việc tại nơi chúng tôi sử dụng bạn bè để gây ảnh hưởng tốt. Tôi đã làm việc trong bộ phận khung của chúng tôi, chúng tôi đã tạo ra một hệ thống cấp độ động cơ cơ bản trên hệ điều hành tùy chỉnh của chúng tôi. Trong nội bộ chúng tôi đã có một cấu trúc lớp:

         Game
        /    \
 TwoPlayer  SinglePlayer

Tất cả các lớp này là một phần của khung và được duy trì bởi nhóm của chúng tôi. Các trò chơi do công ty sản xuất được xây dựng dựa trên khung này bắt nguồn từ một trong những đứa trẻ của Games. Vấn đề là Game có giao diện với nhiều thứ khác nhau mà SinglePlayer và TwoPlayer cần truy cập nhưng chúng tôi không muốn phơi bày bên ngoài các lớp khung. Giải pháp là làm cho các giao diện đó ở chế độ riêng tư và cho phép TwoPlayer và SinglePlayer truy cập chúng thông qua tình bạn.

Thực sự toàn bộ vấn đề này có thể đã được giải quyết bằng cách triển khai tốt hơn hệ thống của chúng tôi nhưng chúng tôi đã bị khóa vào những gì chúng tôi có.


4

Câu trả lời ngắn gọn sẽ là: sử dụng người bạn khi nó thực sự cải thiện việc đóng gói. Cải thiện khả năng đọc và khả năng sử dụng (toán tử << và >> là ví dụ điển hình) cũng là một lý do chính đáng.

Đối với các ví dụ về cải thiện đóng gói, các lớp được thiết kế đặc biệt để làm việc với các phần bên trong của các lớp khác (các lớp kiểm tra đến với tâm trí) là những ứng cử viên tốt.


" Toán tử << và >> là ví dụ chính tắc " Không . Ví dụ truy cập khá chính tắc .
tò mò

@cquilguy: các nhà khai thác <<>>thường là bạn bè, thay vì thành viên, vì việc biến họ thành thành viên sẽ khiến họ khó sử dụng. Tất nhiên, tôi đang nói về trường hợp khi các nhà khai thác cần truy cập dữ liệu riêng tư; nếu không, tình bạn là vô dụng.
Gorpik

" Bởi vì làm cho họ thành viên sẽ khiến họ khó sử dụng. " Rõ ràng, việc tạo operator<<operator>>các thành viên của lớp giá trị thay vì không phải thành viên, hoặc thành viên của i|ostream, sẽ không cung cấp cú pháp mong muốn và tôi không gợi ý. " Tôi đang nói về trường hợp khi các nhà khai thác đó cần truy cập dữ liệu riêng tư " Tôi không hiểu tại sao các nhà khai thác đầu vào / đầu ra sẽ cần truy cập vào các thành viên tư nhân.
tò mò

4

Người tạo ra C ++ nói rằng không môi giới bất kỳ nguyên tắc đóng gói nào, và tôi sẽ trích dẫn anh ta:

"Bạn bè" có vi phạm đóng gói không? Không nó không. "Bạn bè" là một cơ chế rõ ràng để cấp quyền truy cập, giống như thành viên. Bạn không thể (trong một chương trình tuân thủ tiêu chuẩn) cấp cho mình quyền truy cập vào một lớp mà không sửa đổi nguồn của nó.

Rõ ràng hơn ...


@cperedguy: Ngay cả trong trường hợp mẫu, nó là sự thật.
Nawaz

@Nawaz Mẫu tình bạn có thể được cấp, nhưng bất kỳ ai cũng có thể thực hiện một chuyên môn mới một phần hoặc rõ ràng mà không cần sửa đổi lớp cấp tình bạn. Nhưng hãy cẩn thận với các vi phạm ODR khi bạn làm điều đó. Và đừng làm điều đó.
tò mò

3

Một cách sử dụng khác: friend (+ thừa kế ảo) có thể được sử dụng để tránh xuất phát từ một lớp (hay còn gọi là "làm cho một lớp không thể hiểu được") => 1 , 2

Từ 2 :

 class Fred;

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

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

3

Để làm TDD nhiều lần tôi đã sử dụng từ khóa 'bạn bè' trong C ++.

Một người bạn có thể biết tất cả mọi thứ về tôi?


Cập nhật: Tôi tìm thấy câu trả lời có giá trị này về từ khóa "bạn bè" từ trang web Bjarne Stroustrup .

"Bạn bè" là một cơ chế rõ ràng để cấp quyền truy cập, giống như thành viên.


3

Bạn phải rất cẩn thận về thời gian / nơi bạn sử dụng friendtừ khóa, và giống như bạn, tôi đã sử dụng nó rất hiếm khi. Dưới đây là một số lưu ý về việc sử dụng friendvà các lựa chọn thay thế.

Giả sử bạn muốn so sánh hai đối tượng để xem chúng có bằng nhau không. Bạn có thể:

  • Sử dụng các phương thức accessor để thực hiện so sánh (kiểm tra mọi ivar và xác định đẳng thức).
  • Hoặc, bạn có thể truy cập tất cả các thành viên trực tiếp bằng cách công khai chúng.

Vấn đề với tùy chọn đầu tiên, đó có thể là RẤT NHIỀU người truy cập, chậm hơn một chút so với truy cập biến trực tiếp, khó đọc hơn và cồng kềnh. Vấn đề với cách tiếp cận thứ hai là bạn hoàn toàn phá vỡ đóng gói.

Điều tuyệt vời là nếu chúng ta có thể định nghĩa một hàm bên ngoài vẫn có thể truy cập vào các thành viên riêng của một lớp. Chúng tôi có thể làm điều này với friendtừ khóa:

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

Phương thức này equal(Beer, Beer)hiện có quyền truy cập trực tiếp vào abcác thành viên riêng tư (có thể là char *brand, float percentAlcoholv.v. Đây là một ví dụ khá khó hiểu, bạn sẽ sớm áp dụng friendcho một tình trạng quá tải == operator, nhưng chúng ta sẽ làm được điều đó.

Một số điều cần lưu ý:

  • A friendKHÔNG phải là một thành viên của lớp
  • Đây là một chức năng thông thường với quyền truy cập đặc biệt đến các thành viên riêng của lớp
  • Đừng thay thế tất cả người truy cập và người biến đổi bằng bạn bè (bạn cũng có thể tạo ra mọi thứ public!)
  • Tình bạn không có đi có lại
  • Tình bạn không phải là bắc cầu
  • Tình bạn không được thừa hưởng
  • Hoặc, như Câu hỏi thường gặp về C ++ giải thích : "Chỉ vì tôi cấp cho bạn quyền truy cập tình bạn với tôi không tự động cấp cho con bạn quyền truy cập của tôi, không tự động cấp cho bạn bè quyền truy cập của tôi và không tự động cấp cho tôi quyền truy cập vào bạn . "

Tôi chỉ thực sự sử dụng friendskhi khó khăn hơn nhiều để làm điều đó theo cách khác. Một ví dụ khác, nhiều toán vector chức năng thường được tạo ra như là friendsdo khả năng tương tác của Mat2x2, Mat3x3, Mat4x4, Vec2, Vec3, Vec4, vv Và nó chỉ trở nên dễ dàng hơn nhiều để được bạn bè, chứ không phải là phải sử dụng accessors ở khắp mọi nơi. Như đã chỉ ra, friendthường hữu ích khi áp dụng cho <<(thực sự tiện dụng để gỡ lỗi) >>và có thể là ==toán tử, nhưng cũng có thể được sử dụng cho một cái gì đó như thế này:

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

Như tôi nói, tôi không sử dụng friendrất thường xuyên, nhưng mọi lúc và sau đó chỉ là những gì bạn cần. Hi vọng điêu nay co ich!


2

Liên quan đến toán tử << và toán tử >> không có lý do chính đáng nào để kết bạn với các toán tử này. Đúng là họ không nên là thành viên, nhưng họ cũng không cần phải là bạn bè.

Điều tốt nhất để làm là tạo các chức năng in công khai (lasream &) và đọc (iux &). Sau đó, viết toán tử << và toán tử >> theo các hàm đó. Điều này mang lại lợi ích bổ sung cho phép bạn biến những chức năng đó thành ảo, cung cấp tuần tự hóa ảo.


" Liên quan đến toán tử << và toán tử >> không có lý do chính đáng nào để kết bạn với các toán tử này. " Hoàn toàn chính xác. " Điều này mang lại lợi ích gia tăng khi cho phép bạn biến các chức năng đó thành ảo " , Nếu lớp được đề cập là dành cho phái sinh, vâng. Nếu không, tại sao phải bận tâm?
tò mò

Tôi thực sự không hiểu tại sao câu trả lời này bị hạ thấp hai lần - và thậm chí không có lời giải thích! Điều đó thật thô lỗ.
tò mò

ảo sẽ thêm một cú đánh hoàn hảo có thể khá lớn trong việc xê
ri

2

Tôi chỉ sử dụng từ khóa bạn bè cho các chức năng được bảo vệ nhất. Một số người sẽ nói rằng bạn không nên kiểm tra chức năng được bảo vệ. Tôi, tuy nhiên, tìm thấy công cụ rất hữu ích này khi thêm chức năng mới.

Tuy nhiên, tôi không sử dụng từ khóa trực tiếp trong các khai báo lớp, thay vào đó tôi sử dụng một mẫu hack tiện lợi để đạt được điều này:

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

Điều này cho phép tôi làm như sau:

friendMe(this, someClassInstance).someProtectedFunction();

Hoạt động trên GCC và MSVC ít nhất.


2

Trong C ++, từ khóa "friend" rất hữu ích trong việc nạp chồng toán tử và tạo cầu.

1.) Từ khóa bạn bè trong quá tải toán tử:
Ví dụ cho quá tải toán tử là: Giả sử chúng ta có một lớp "Điểm" có hai biến float
"x" (đối với tọa độ x) và "y" (đối với tọa độ y). Bây giờ chúng ta phải quá tải "<<"(toán tử trích xuất) sao cho nếu chúng ta gọi"cout << pointobj" thì nó sẽ in tọa độ x và y (trong đó pointobj là một đối tượng của Điểm lớp). Để làm điều này, chúng tôi có hai lựa chọn:

   1.Overload "toán tử << ()" trong lớp "Ostream".
   2.Overload "toán tử << ()" hàm trong lớp "Point".
Bây giờ Tùy chọn đầu tiên không tốt vì nếu chúng ta cần quá tải lại toán tử này cho một số lớp khác thì chúng ta lại phải thực hiện thay đổi trong lớp "Ostream".
Đó là lý do tại sao thứ hai là lựa chọn tốt nhất. Bây giờ trình biên dịch có thể gọi "operator <<()" hàm:

   1.Sử dụng đối tượng Ostream cout.As: cout.operator << (Pointobj) (hình thức lớp Ostream). 
2.Call không có đối tượng.As: toán tử << (cout, Pointobj) (từ lớp Point).

Vì chúng tôi đã thực hiện quá tải trong lớp Point. Vì vậy, để gọi hàm này mà không có đối tượng, chúng ta phải thêm "friend"từ khóa vì chúng ta có thể gọi hàm bạn bè mà không cần đối tượng. Bây giờ khai báo hàm sẽ là:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"

2.) Từ khóa bạn bè trong việc tạo cầu nối:
Giả sử chúng ta phải tạo một hàm trong đó chúng ta phải truy cập thành viên riêng của hai hoặc nhiều lớp (thường được gọi là "cầu nối"). Cách thực hiện:
Để truy cập thành viên riêng của một lớp, nó phải là thành viên của lớp đó. Bây giờ để truy cập thành viên riêng của lớp khác, mỗi lớp nên khai báo hàm đó là hàm bạn bè. Ví dụ: Giả sử có hai lớp A và B. Một hàm "funcBridge()"muốn truy cập thành viên riêng của cả hai lớp. Sau đó cả hai lớp nên khai báo "funcBridge()"là:
friend return_type funcBridge(A &a_obj, B & b_obj);

Tôi nghĩ rằng điều này sẽ giúp để hiểu từ khóa bạn bè.


2

Như tài liệu tham khảo cho tuyên bố bạn bè nói:

Khai báo bạn bè xuất hiện trong một lớp cơ thể và cấp một chức năng hoặc một lớp khác truy cập vào các thành viên riêng tư và được bảo vệ của lớp nơi khai báo bạn bè xuất hiện.

Vì vậy, như một lời nhắc nhở, có một số lỗi kỹ thuật trong một số câu trả lời friendchỉ có thể truy cập các thành viên được bảo vệ .


1

Ví dụ về cây là một ví dụ khá hay: Có một đối tượng được triển khai trong một vài lớp khác nhau mà không có mối quan hệ thừa kế.

Có lẽ bạn cũng có thể cần nó để có một nhà xây dựng được bảo vệ và buộc mọi người sử dụng nhà máy "bạn bè" của bạn.

... Ok, thật lòng mà nói bạn có thể sống mà không cần nó.


1

Để làm TDD nhiều lần tôi đã sử dụng từ khóa 'bạn bè' trong C ++.
Một người bạn có thể biết tất cả mọi thứ về tôi?

Không, đó chỉ là tình bạn một chiều: `(


1

Một ví dụ cụ thể mà tôi sử dụng friendlà khi tạo các lớp Singleton . Các friendtừ khóa cho phép tôi tạo ra một chức năng accessor, đó là ngắn gọn hơn là luôn luôn có một "getInstance ()" phương pháp trên lớp.

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

Đây có thể là một vấn đề về hương vị, nhưng tôi không nghĩ việc lưu một vài tổ hợp phím sẽ biện minh cho việc sử dụng bạn bè ở đây. GetMySingleton () nên là một phương thức tĩnh của lớp.
Gorpik

C-tor riêng sẽ không cho phép chức năng không kết bạn để khởi tạo MySingleton, vì vậy từ khóa bạn bè là cần thiết ở đây.
JBRWilkinson

@Gorpik " Đây có thể là vấn đề về sở thích, nhưng tôi không nghĩ việc lưu một vài tổ hợp phím sẽ biện minh cho việc sử dụng bạn bè ở đây. " Dù sao, friendkhông không cần một "biện minh" Đặc biệt, khi thêm một hàm thành viên không.
tò mò

Dù sao thì Singletons cũng bị coi là một hành vi xấu (Google "singleton có hại" và bạn sẽ nhận được rất nhiều kết quả như thế này . Tôi không nghĩ việc sử dụng một tính năng để triển khai một antipotype có thể được coi là sử dụng tốt tính năng đó.
weberc2

1

Các hàm và lớp bạn bè cung cấp quyền truy cập trực tiếp vào các thành viên riêng tư và được bảo vệ của lớp để tránh phá vỡ đóng gói trong trường hợp chung. Hầu hết việc sử dụng là với Ostream: chúng tôi muốn có thể gõ:

Point p;
cout << p;

Tuy nhiên, điều này có thể yêu cầu quyền truy cập vào dữ liệu riêng tư của Điểm, vì vậy chúng tôi xác định toán tử bị quá tải

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

Có ý nghĩa đóng gói rõ ràng, tuy nhiên. Đầu tiên, bây giờ lớp bạn hoặc hàm có quyền truy cập đầy đủ vào TẤT CẢ các thành viên của lớp, ngay cả những người không liên quan đến nhu cầu của nó. Thứ hai, việc triển khai lớp và người bạn hiện đang được đưa ra đến mức thay đổi nội bộ trong lớp có thể phá vỡ người bạn.

Nếu bạn xem người bạn như một phần mở rộng của lớp, thì đây không phải là vấn đề, nói một cách logic. Nhưng, trong trường hợp đó, tại sao lại cần phải loại bỏ người bạn ngay từ đầu.

Để đạt được điều tương tự mà mục đích của 'bạn bè' cần đạt được, nhưng không phá vỡ sự đóng gói, người ta có thể làm điều này:

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

Đóng gói không bị hỏng, lớp B không có quyền truy cập vào triển khai nội bộ trong A, tuy nhiên kết quả giống như khi chúng ta khai báo B là bạn của A. Trình biên dịch sẽ tối ưu hóa các lệnh gọi hàm, vì vậy điều này sẽ dẫn đến kết quả tương tự hướng dẫn như truy cập trực tiếp.

Tôi nghĩ rằng việc sử dụng 'bạn bè' chỉ đơn giản là một lối tắt với lợi ích có thể tranh cãi, nhưng chi phí nhất định.


1

Đây có thể không phải là tình huống sử dụng thực tế nhưng có thể giúp minh họa việc sử dụng bạn bè giữa các lớp.

Câu lạc bộ

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

Các lớp thành viên

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

Tiện nghi

class Amenity{};

Nếu bạn nhìn vào mối quan hệ của các lớp này ở đây; ClubHouse có nhiều loại thành viên khác nhau và quyền truy cập thành viên. Tất cả các Thành viên đều có nguồn gốc từ một siêu lớp hoặc lớp cơ sở vì tất cả họ đều chia sẻ một ID và một kiểu liệt kê phổ biến và các lớp bên ngoài có thể truy cập ID và Loại của họ thông qua các hàm truy cập được tìm thấy trong lớp cơ sở.

Tuy nhiên, thông qua hệ thống phân cấp này của các Thành viên và các lớp Xuất phát và mối quan hệ của họ với lớp ClubHouse, người duy nhất trong lớp dẫn xuất có "đặc quyền" là lớp VIPMember. Lớp cơ sở và 2 lớp dẫn xuất khác không thể truy cập vào phương thức joVIPEvent () của ClubHouse, nhưng lớp Thành viên VIP có đặc quyền đó như thể nó có quyền truy cập hoàn toàn vào sự kiện đó.

Vì vậy, với VIPMember và ClubHouse, đây là con đường truy cập hai chiều, nơi các Lớp thành viên khác bị hạn chế.


0

Khi thực hiện các thuật toán cây cho lớp, mã khung mà prof đã cho chúng ta có lớp cây như một người bạn của lớp nút.

Nó thực sự không làm được gì, ngoài việc cho phép bạn truy cập vào một biến thành viên mà không cần sử dụng chức năng cài đặt.


0

Bạn có thể sử dụng tình bạn khi các lớp khác nhau (không kế thừa từ lớp này) đang sử dụng các thành viên riêng tư hoặc được bảo vệ của lớp khác.

Các trường hợp sử dụng điển hình của các chức năng bạn bè là các hoạt động được tiến hành giữa hai lớp khác nhau truy cập các thành viên riêng hoặc được bảo vệ của cả hai.

từ http://www.cplusplus.com/doc/tutorial/inherribution/ .

Bạn có thể xem ví dụ này khi phương thức không phải thành viên truy cập vào các thành viên riêng của một lớp. Phương thức này phải được khai báo trong chính lớp này với tư cách là một người bạn của lớp.

// 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

Có lẽ tôi đã bỏ lỡ điều gì đó từ các câu trả lời ở trên nhưng một khái niệm quan trọng khác trong đóng gói là che giấu việc thực hiện. Giảm quyền truy cập vào các thành viên dữ liệu riêng tư (chi tiết triển khai của một lớp) cho phép sửa đổi mã dễ dàng hơn nhiều sau này. Nếu một người bạn truy cập trực tiếp vào dữ liệu riêng tư, bất kỳ thay đổi nào đối với các trường dữ liệu triển khai (dữ liệu riêng tư), hãy phá vỡ mã truy cập dữ liệu đó. Sử dụng các phương pháp truy cập chủ yếu loại bỏ điều này. Khá quan trọng tôi sẽ nghĩ.


-1

Bạn có thể tuân theo những nguyên tắc nghiêm ngặt nhất và tinh khiết nhất OOP và đảm bảo rằng không có thành viên dữ liệu cho bất kỳ lớp thậm chí có accessors sao cho tất cả đối tượng phải là những người duy nhất có thể biết về dữ liệu của họ với cách duy nhất để thực hiện chúng là thông qua gián tiếp thông điệp , tức là phương pháp.

Nhưng ngay cả C # cũng có một từ khóa hiển thị nội bộ và Java có khả năng truy cập mức gói mặc định cho một số thứ. C ++ thực sự đến gần hơn với lý tưởng OOP bằng cách minimizinbg thỏa hiệp về khả năng hiển thị vào một lớp bằng cách chỉ định chính xác lớp nào khác và chỉ các lớp khác có thể nhìn thấy nó.

Tôi không thực sự sử dụng C ++ nhưng nếu C # có bạn bè thì tôi sẽ làm điều đó thay vì công cụ sửa đổi nội bộ toàn cầu , mà tôi thực sự sử dụng rất nhiều. Nó không thực sự phá vỡ sự đóng gói, bởi vì đơn vị triển khai trong .NET một hội đồng.

Nhưng sau đó, có Thuộc tính InternalsVisibleTo (otherAssugging) hoạt động như một cơ chế kết bạn chéo . Microsoft sử dụng điều này cho các hội đồng thiết kế trực quan .


-1

Bạn bè cũng hữu ích cho các cuộc gọi lại. Bạn có thể thực hiện các cuộc gọi lại như các phương thức tĩnh

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

nơi callbackgọi localCallbacknội bộ, vàclientData có ví dụ của bạn trong đó. Theo ý kiến ​​của tôi,

hoặc là...

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

Điều này cho phép người bạn được xác định hoàn toàn trong cpp là hàm kiểu c và không làm lộn xộn lớp.

Tương tự, một mẫu mà tôi đã thấy rất thường xuyên là đưa tất cả các thành viên thực sự riêng tư của một lớp vào một lớp khác, được khai báo trong tiêu đề, được xác định trong cpp và kết bạn. Điều này cho phép bộ mã hóa ẩn rất nhiều sự phức tạp và hoạt động bên trong của lớp khỏi người dùng của tiêu đề.

Trong tiêu đề:

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

Trong cpp,

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

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

Nó trở nên dễ dàng hơn để che giấu những thứ mà hạ lưu không cần phải nhìn theo cách này.


1
Giao diện sẽ không phải là một cách sạch hơn để đạt được điều này? Điều gì để ngăn ai đó tra cứu MyFooPrivate.h?
JBRWilkinson

1
Chà, nếu bạn đang sử dụng riêng tư và công khai để giữ bí mật, bạn sẽ dễ dàng bị đánh bại. Bằng cách "ẩn", ý tôi là, người dùng MyFoo không thực sự cần phải xem các thành viên riêng. Bên cạnh đó, thật hữu ích để duy trì khả năng tương thích ABI. Nếu bạn biến _private thành một con trỏ, việc triển khai riêng tư có thể thay đổi nhiều như bạn muốn, mà không cần chạm vào giao diện chung, do đó giữ khả năng tương thích ABI.
shash

Bạn đang đề cập đến thành ngữ PIMPL; mục đích không phải là đóng gói bổ sung như bạn có thể nói, nhưng để di chuyển các chi tiết triển khai ra khỏi tiêu đề để thay đổi một chi tiết triển khai không buộc phải biên dịch lại mã máy khách. Hơn nữa, không cần sử dụng bạn bè để thực hiện thành ngữ này.
weberc2

Vâng, vâng. Mục đích chính của nó là để di chuyển chi tiết thực hiện. Người bạn ở đó rất hữu ích để xử lý các thành viên riêng trong lớp công khai từ cách riêng tư hoặc ngược lại.
xáo trộn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.