Kapan sebaiknya Anda menggunakan 'teman' di C ++?

354

Saya telah membaca C ++ FAQ dan ingin tahu tentang frienddeklarasi tersebut. Saya pribadi belum pernah menggunakannya, namun saya tertarik menjelajahi bahasa.

Apa contoh yang baik untuk digunakan friend?


Membaca FAQ sedikit lebih lama Saya suka gagasan tentang << >>operator kelebihan dan menambahkan sebagai teman dari kelas-kelas itu. Namun saya tidak yakin bagaimana ini tidak merusak enkapsulasi. Kapan pengecualian ini bisa tetap dalam ketatnya yaitu OOP?

Peter Mortensen
sumber
5
Sementara saya setuju dengan jawaban bahwa kelas teman belum tentu A Bad Thing, saya cenderung memperlakukannya sebagai kode kecil. Seringkali, meskipun tidak selalu, menunjukkan bahwa hirarki kelas perlu dipertimbangkan kembali.
Mawg mengatakan mengembalikan Monica
1
Anda akan menggunakan kelas teman di mana sudah ada kopling ketat. Untuk itulah dibuat. Sebagai contoh, tabel database dan indeksnya sangat erat digabungkan. Ketika tabel berubah, semua indeksnya harus diperbarui. Jadi, kelas DBIndex akan mendeklarasikan DBTable sebagai teman sehingga DBTable dapat mengakses indeks internal secara langsung. Tetapi tidak akan ada antarmuka publik untuk DBIndex; bahkan tidak masuk akal untuk membaca indeks.
shawnhcorey
OOP "purist" dengan sedikit pengalaman praktis berpendapat bahwa teman melanggar prinsip-prinsip OOP, karena kelas harus menjadi satu-satunya pengelola negara privatnya. Ini baik-baik saja, sampai Anda menemukan situasi bersama di mana dua kelas perlu mempertahankan negara privat bersama.
kaalus

Jawaban:

335

Pertama (IMO) tidak mendengarkan orang yang mengatakan frienditu tidak berguna. Ini berguna. Dalam banyak situasi Anda akan memiliki objek dengan data atau fungsionalitas yang tidak dimaksudkan untuk tersedia untuk umum. Hal ini terutama berlaku untuk basis kode besar dengan banyak penulis yang mungkin hanya terbiasa dengan bidang yang berbeda.

Ada ADALAH alternatif untuk teman penspesifikasi, tetapi sering kali tidak praktis (kelas beton tingkat cpp / typedef bertopeng) atau tidak mudah (komentar atau konvensi nama fungsi).

Ke jawabannya;

The friendspecifier memungkinkan akses kelas yang ditunjuk untuk data atau fungsi lindung dalam kelas membuat pernyataan teman. Misalnya dalam kode di bawah ini siapa pun dapat meminta nama anak, tetapi hanya ibu dan anak yang dapat mengubah nama.

Anda dapat mengambil contoh sederhana ini lebih jauh dengan mempertimbangkan kelas yang lebih kompleks seperti Window. Kemungkinan besar Window akan memiliki banyak elemen fungsi / data yang tidak boleh diakses publik, tetapi ADA dibutuhkan oleh kelas terkait seperti 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 );
};
Andrew Grant
sumber
114
Sebagai catatan tambahan, C ++ FAQ menyebutkan bahwa friend meningkatkan enkapsulasi. friendmemberikan akses selektif kepada anggota, seperti protectedhalnya. Setiap kontrol yang lebih baik lebih baik daripada memberikan akses publik. Bahasa lain juga mendefinisikan mekanisme akses selektif, pertimbangkan C # internal. Sebagian besar kritik negatif tentang penggunaan friendterkait dengan kopling ketat, yang umumnya dipandang sebagai hal yang buruk. Namun, dalam beberapa kasus, kopling ketat justru yang Anda inginkan dan friendmemberi Anda kekuatan itu.
André Caron
5
Bisakah Anda mengatakan lebih banyak tentang (kelas beton bertingkat cpp) dan (typedef bertopeng), Andrew ?
OmarOthman
18
Jawaban ini tampaknya lebih fokus pada menjelaskan apa friendyang bukan memberikan contoh yang memotivasi . Contoh Window / WindowManager lebih baik dari contoh yang ditunjukkan, tetapi terlalu kabur. Jawaban ini juga tidak membahas bagian enkapsulasi dari pertanyaan tersebut.
bames53
4
Jadi ada 'teman' yang efektif karena C ++ tidak memiliki gagasan tentang paket di mana semua anggota dapat berbagi rincian implementasi? Saya akan sangat tertarik dengan contoh dunia nyata.
weberc2
1
Saya pikir contoh Ibu / Anak sangat disayangkan. Nama-nama ini cocok untuk instance, tetapi tidak untuk kelas. (Masalahnya menunjukkan jika kita memiliki 2 ibu, dan masing-masing memiliki anak sendiri).
Jo So
162

Di tempat kerja kami menggunakan teman untuk menguji kode , secara ekstensif. Ini berarti kami dapat menyediakan enkapsulasi dan penyembunyian informasi yang tepat untuk kode aplikasi utama. Tetapi kita juga dapat memiliki kode tes terpisah yang menggunakan teman untuk memeriksa keadaan internal dan data untuk pengujian.

Cukuplah untuk mengatakan saya tidak akan menggunakan kata kunci teman sebagai komponen penting dari desain Anda.

Daemin
sumber
Untuk itulah saya menggunakannya. Itu atau hanya mengatur variabel anggota untuk dilindungi. Hanya memalukan itu tidak bekerja untuk C ++ / CLI :-(
Jon Cage
12
Secara pribadi saya akan mencegah ini. Biasanya Anda menguji antarmuka, yaitu apakah satu set input memberikan set output yang diharapkan. Mengapa Anda perlu memeriksa data internal?
Graeme
55
@Graeme: Karena rencana pengujian yang baik mencakup pengujian kotak putih dan kotak hitam.
Ben Voigt
1
Saya cenderung setuju dengan @Graeme, seperti yang dijelaskan dengan sempurna dalam jawaban ini .
Alexis Leclerc
2
@Graeme itu mungkin bukan data internal secara langsung. Saya mungkin metode yang melakukan operasi atau tugas tertentu pada data di mana metode itu pribadi untuk kelas dan tidak boleh diakses publik sementara beberapa objek lain mungkin perlu memberi makan atau benih metode yang dilindungi kelas dengan data sendiri.
Francis Cugler
93

Kata friendkunci memiliki sejumlah kegunaan yang baik. Berikut adalah dua kegunaan yang langsung terlihat oleh saya:

Definisi Teman

Definisi teman memungkinkan untuk mendefinisikan suatu fungsi dalam lingkup kelas, tetapi fungsi tersebut tidak akan didefinisikan sebagai fungsi anggota, tetapi sebagai fungsi bebas dari namespace terlampir, dan tidak akan terlihat secara normal kecuali untuk pencarian bergantung argumen. Itu membuatnya sangat berguna untuk overloading operator:

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
}

Kelas Dasar CRTP Pribadi

Terkadang, Anda menemukan bahwa suatu kebijakan membutuhkan akses ke kelas turunan:

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

Anda akan menemukan contoh yang tidak dibuat-buat untuk itu dalam jawaban ini . Penggunaan kode lain dalam jawaban ini . Basis CRTP melemparkan pointer ini, untuk dapat mengakses bidang data dari kelas turunan menggunakan data-anggota-pointer.

Johannes Schaub - litb
sumber
Hai, saya mendapatkan kesalahan sintaks (dalam xcode 4) ketika saya mencoba CRTP Anda. Xcode yakin saya mencoba mewarisi templat kelas. Kesalahan terjadi di P<C>dalam template<template<typename> class P> class C : P<C> {};menyatakan "Penggunaan template kelas C membutuhkan argumen template". Apakah Anda memiliki masalah yang sama atau mungkin tahu solusinya?
bennedich
@Bennedich pada pandangan pertama, yang tampak seperti jenis kesalahan yang akan Anda dapatkan dengan dukungan fitur C ++ yang tidak memadai. Yang cukup umum di antara kompiler. Penggunaan FlexibleClassdalam FlexibleClassharus secara implisit merujuk pada tipenya sendiri.
Yakk - Adam Nevraumont
@bennedich: Aturan untuk penggunaan nama templat kelas dari dalam tubuh kelas diubah dengan C ++ 11. Coba aktifkan mode C ++ 11 di kompiler Anda.
Ben Voigt
Di Visual Studio 2015 tambahkan publik ini: f () {}; f (int_type t): value (t) {}; Untuk mencegah kesalahan kompiler ini: error C2440: '<function-style-cast>': tidak dapat mengkonversi dari 'utils :: f :: int_type' ke 'utils :: f' note: Tidak ada konstruktor yang dapat mengambil tipe sumber, atau konstruktor resolusi kelebihan adalah ambigu
Damian
41

@roo : Enkapsulasi tidak rusak di sini karena kelas itu sendiri menentukan siapa yang dapat mengakses anggota pribadinya. Enkapsulasi hanya akan rusak jika ini bisa disebabkan dari luar kelas, misalnya jika Anda operator <<akan menyatakan "Saya seorang teman kelas foo."

friendmenggantikan penggunaan public, bukan penggunaan private!

Sebenarnya, FAQ C ++ sudah menjawab ini .

Konrad Rudolph
sumber
14
"! teman Menggantikan penggunaan umum, tidak menggunakan swasta", saya kedua yang
Waleed Eissa
26
@Assaf: ya, tapi FQA, untuk sebagian besar, banyak omong kosong yang tidak jelas tanpa nilai nyata. Bagian pada friendtidak terkecuali. Satu-satunya pengamatan nyata di sini adalah bahwa C ++ memastikan enkapsulasi hanya pada waktu kompilasi. Dan Anda tidak perlu kata-kata lagi untuk mengatakannya. Sisanya adalah bollocks. Jadi, secara ringkas: bagian FQA ini tidak layak disebut.
Konrad Rudolph
12
Sebagian besar dari FQA itu adalah blx :)
rama-jka toti
1
@Konrad: "Satu-satunya pengamatan nyata di sini adalah bahwa C ++ memastikan enkapsulasi pada waktu kompilasi saja." Apakah ada bahasa yang memastikan ini saat runtime? Sejauh yang saya tahu, mengembalikan referensi ke anggota pribadi (dan fungsi, untuk bahasa yang memungkinkan pointer ke fungsi atau fungsi sebagai objek kelas satu) diizinkan dalam C #, Java, Python dan banyak lainnya.
André Caron
@ André: JVM dan CLR sebenarnya dapat memastikan hal ini sejauh yang saya tahu. Saya tidak tahu apakah itu selalu dilakukan tetapi Anda diduga dapat melindungi paket / rakitan dari gangguan seperti itu (tidak pernah melakukannya sendiri).
Konrad Rudolph
27

Contoh kanonik adalah untuk membebani operator <<. Penggunaan umum lainnya adalah untuk memungkinkan akses kelas pembantu atau admin ke bagian internal Anda.

Berikut adalah beberapa panduan yang saya dengar tentang teman C ++. Yang terakhir sangat mengesankan.

  • Teman Anda bukan teman anak Anda.
  • Teman anak Anda bukan teman Anda.
  • Hanya teman yang dapat menyentuh bagian pribadi Anda.
Mark Harrison
sumber
" Contoh kanonik adalah untuk membebani operator <<. " Kanonik tidak menggunakan friendkurasa.
curiousguy
16

sunting: Membaca faq sedikit lebih lama Saya menyukai gagasan tentang << >> operator overloading dan menambahkan sebagai teman dari kelas-kelas itu, namun saya tidak yakin bagaimana ini tidak merusak enkapsulasi

Bagaimana cara memecahkan enkapsulasi?

Anda memecahkan enkapsulasi ketika Anda mengizinkan akses tidak terbatas ke anggota data. Pertimbangkan kelas-kelas berikut:

class c1 {
public:
  int x;
};

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

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

c1adalah jelas tidak dikemas. Siapa pun dapat membaca dan memodifikasi xdi dalamnya. Kami tidak memiliki cara untuk menegakkan segala jenis kontrol akses.

c2jelas dienkapsulasi. Tidak ada akses publik untuk x. Yang dapat Anda lakukan adalah memanggil foofungsi, yang melakukan beberapa operasi yang berarti di kelas .

c3? Apakah itu kurang dienkapsulasi? Apakah ini memungkinkan akses tanpa batas ke x? Apakah ini memungkinkan akses fungsi yang tidak diketahui?

Tidak. Ini memungkinkan tepat satu fungsi untuk mengakses anggota pribadi kelas. Sama seperti c2itu. Dan sama seperti c2, satu fungsi yang memiliki akses bukan "beberapa fungsi acak, tidak dikenal", tetapi "fungsi yang tercantum dalam definisi kelas". Sama seperti c2, kita dapat melihat, hanya dengan melihat definisi kelas, daftar lengkap siapa yang memiliki akses.

Jadi bagaimana tepatnya ini kurang dienkapsulasi? Jumlah kode yang sama memiliki akses ke anggota pribadi kelas. Dan setiap orang yang memiliki akses tercantum dalam definisi kelas.

friendtidak merusak enkapsulasi. Itu membuat beberapa programmer orang Jawa merasa tidak nyaman, karena ketika mereka mengatakan "OOP", mereka sebenarnya berarti "Java". Ketika mereka mengatakan "Enkapsulasi", mereka tidak berarti "anggota pribadi harus dilindungi dari akses sewenang-wenang", tetapi "kelas Java di mana satu-satunya fungsi yang dapat mengakses anggota pribadi, adalah anggota kelas", meskipun ini omong kosong untuk beberapa alasan .

Pertama, seperti yang sudah ditunjukkan, itu terlalu membatasi. Tidak ada alasan mengapa metode teman tidak boleh diizinkan untuk melakukan hal yang sama.

Kedua, itu tidak cukup ketat . Pertimbangkan kelas keempat:

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

Ini, menurut mentalitas Jawa yang disebutkan di atas, dirangkum dengan sempurna. Namun, ini benar-benar memungkinkan siapa saja untuk membaca dan memodifikasi x . Bagaimana itu masuk akal? (petunjuk: Tidak)

Intinya: Enkapsulasi adalah tentang dapat mengontrol fungsi mana yang dapat mengakses anggota pribadi. Ini bukan tentang di mana definisi fungsi-fungsi ini berada.

jalf
sumber
10

Versi umum lain dari contoh Andrew, kode-kopling yang ditakuti

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

Alih-alih khawatir jika kedua baris selalu dilakukan bersama dan dalam urutan yang konsisten, Anda bisa menjadikan metode pribadi dan memiliki fungsi teman untuk menegakkan konsistensi:

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

Dengan kata lain Anda dapat menjaga antarmuka publik lebih kecil dan memberlakukan invarian yang melintasi kelas dan objek dalam fungsi teman.

maccullt
sumber
6
Kenapa ada yang butuh teman untuk itu? Mengapa tidak membiarkan addChildfungsi anggota juga mengatur induknya?
Nawaz
1
Contoh yang lebih baik setParentadalah berteman, karena Anda tidak ingin mengizinkan klien untuk mengubah orang tua karena Anda akan mengelolanya di addChild/ removeChildkategori fungsi.
Ylisar
8

Anda mengontrol hak akses untuk anggota dan fungsi menggunakan hak Pribadi / Dilindungi / Publik? jadi dengan asumsi ide masing-masing dan masing-masing dari 3 level itu jelas, maka harus jelas bahwa kita kehilangan sesuatu ...

Deklarasi anggota / fungsi sebagai dilindungi misalnya cukup umum. Anda mengatakan bahwa fungsi ini di luar jangkauan untuk semua orang (kecuali untuk anak yang diwarisi tentu saja). Tapi bagaimana dengan pengecualian? setiap sistem keamanan memungkinkan Anda memiliki semacam 'daftar putih' bukan?

Jadi teman memungkinkan Anda memiliki fleksibilitas memiliki isolasi benda padat batu, tetapi memungkinkan "celah" untuk dibuat untuk hal-hal yang Anda rasa dibenarkan.

Saya kira orang mengatakan itu tidak diperlukan karena selalu ada desain yang akan dilakukan tanpa itu. Saya pikir itu mirip dengan diskusi variabel global: Anda tidak boleh menggunakannya, Selalu ada cara untuk melakukannya tanpa mereka ... tetapi dalam kenyataannya, Anda melihat kasus-kasus di mana yang akhirnya menjadi (hampir) cara yang paling elegan. .. Saya pikir ini adalah kasus yang sama dengan teman-teman.

Itu tidak benar-benar ada gunanya, selain membiarkan Anda mengakses variabel anggota tanpa menggunakan fungsi pengaturan

baik itu bukan cara untuk melihatnya. Idenya adalah untuk mengontrol siapa yang dapat mengakses apa, memiliki atau tidak fungsi pengaturan tidak ada hubungannya dengan itu.

csmba
sumber
2
Bagaimana friendcelahnya? Ini memungkinkan metode yang tercantum dalam kelas mengakses anggota pribadinya. Masih tidak membiarkan kode arbitrer mengaksesnya. Karena itu tidak berbeda dengan fungsi anggota publik.
jalf
teman sedekat Anda bisa mendapatkan akses tingkat paket C # / Java di C ++. @jalf - bagaimana dengan kelas teman (seperti kelas pabrik)?
Ogre Psalm33
1
@ Ogre: Bagaimana dengan mereka? Anda masih secara khusus memberikan kelas itu dan tidak ada orang lain akses ke internal kelas. Anda tidak hanya membiarkan gerbang terbuka untuk kode arbitrer yang tidak dikenal untuk mengacaukan kelas Anda.
Jalf
8

Saya menemukan tempat praktis untuk menggunakan akses teman: Fungsi pribadi yang paling sedikit.

VladimirS
sumber
Tetapi dapatkah fungsi publik juga digunakan untuk itu? Apa keuntungan menggunakan akses teman?
Zheng Qu
@Maverobot Bisakah Anda menguraikan pertanyaan Anda?
VladimirS
5

Teman menjadi berguna ketika Anda sedang membangun sebuah wadah dan Anda ingin menerapkan iterator untuk kelas itu.

rptony
sumber
4

Kami memiliki masalah menarik yang muncul di perusahaan tempat saya bekerja sebelumnya di mana kami menggunakan teman untuk pengaruh yang layak. Saya bekerja di departemen kerangka kerja kami, kami menciptakan sistem tingkat engine dasar di atas OS kustom kami. Secara internal kami memiliki struktur kelas:

         Game
        /    \
 TwoPlayer  SinglePlayer

Semua kelas ini adalah bagian dari kerangka kerja dan dikelola oleh tim kami. Game yang diproduksi oleh perusahaan dibangun di atas kerangka kerja ini yang berasal dari salah satu anak Game. Masalahnya adalah bahwa Game memiliki antarmuka untuk berbagai hal yang memerlukan akses SinglePlayer dan TwoPlayer tetapi kami tidak ingin mengekspos di luar kelas kerangka kerja. Solusinya adalah menjadikan antarmuka itu pribadi dan memungkinkan TwoPlayer dan SinglePlayer mengaksesnya melalui pertemanan.

Sejujurnya seluruh masalah ini bisa diselesaikan dengan implementasi yang lebih baik dari sistem kami tetapi kami terkunci ke dalam apa yang kami miliki.

sinar
sumber
4

Jawaban singkatnya adalah: gunakan teman saat itu benar-benar meningkatkan enkapsulasi. Meningkatkan keterbacaan dan kegunaan (operator << dan >> adalah contoh kanonik) juga merupakan alasan yang bagus.

Adapun contoh-contoh meningkatkan enkapsulasi, kelas-kelas yang dirancang khusus untuk bekerja dengan internal kelas-kelas lain (kelas-kelas tes muncul di benak) adalah kandidat yang baik.

Gorpik
sumber
" Operator << dan >> adalah contoh kanonik " Tidak. Sebaliknya kanonik contoh kontra .
curiousguy
@curiousguy: operator <<dan >>biasanya berteman, bukan anggota, karena menjadikan mereka anggota akan membuat mereka canggung untuk digunakan. Tentu saja, saya berbicara tentang kasus ketika operator tersebut perlu mengakses data pribadi; jika tidak, persahabatan tidak berguna.
Gorpik
" karena menjadikan mereka anggota akan membuat mereka canggung untuk digunakan. " Jelas, membuat operator<<dan operator>>anggota kelas nilai bukannya bukan anggota, atau anggota i|ostream, tidak akan memberikan sintaks yang diinginkan, dan saya tidak menyarankan itu. " Saya berbicara tentang kasus ketika para operator perlu mengakses data pribadi " Saya tidak begitu mengerti mengapa operator input / output perlu mengakses anggota pribadi.
curiousguy
4

Pencipta C ++ mengatakan itu tidak mempermasalahkan prinsip enkapsulasi apa pun, dan saya akan mengutipnya:

Apakah "teman" melanggar enkapsulasi? Tidak. "Teman" adalah mekanisme eksplisit untuk memberikan akses, seperti halnya keanggotaan. Anda tidak dapat (dalam program penyesuaian standar) memberikan diri Anda akses ke kelas tanpa memodifikasi sumbernya.

Lebih dari jelas ...

garzanti
sumber
@curiousguy: Bahkan dalam kasus templat, itu benar.
Nawaz
@Nawaz Template persahabatan dapat diberikan, tetapi siapa pun dapat membuat spesialisasi parsial atau eksplisit baru tanpa mengubah kelas pemberian pertemanan. Tapi hati-hati dengan pelanggaran ODR saat Anda melakukannya. Dan jangan lakukan itu.
curiousguy
3

Kegunaan lain: teman (+ virtual inheritance) dapat digunakan untuk menghindari diturunkan dari suatu kelas (alias: "buat kelas kurang dapat ditelusuri") => 1 , 2

Dari 2 :

 class Fred;

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

 class Fred : private virtual FredBase {
 public:
   ...
 }; 
Gian Paolo Ghilardi
sumber
3

Untuk melakukan TDD berkali-kali saya menggunakan kata kunci 'teman' di C ++.

Bisakah seorang teman mengetahui segalanya tentang saya?


Diperbarui: Saya menemukan jawaban yang berharga ini tentang kata kunci "teman" dari situs Bjarne Stroustrup .

"Teman" adalah mekanisme eksplisit untuk memberikan akses, seperti halnya keanggotaan.

popopome
sumber
3

Anda harus sangat berhati-hati tentang kapan / di mana Anda menggunakan friendkata kunci, dan, seperti Anda, saya jarang menggunakannya. Di bawah ini adalah beberapa catatan tentang penggunaan frienddan alternatifnya.

Katakanlah Anda ingin membandingkan dua objek untuk melihat apakah keduanya sama. Anda dapat:

  • Gunakan metode accessor untuk melakukan perbandingan (periksa setiap ivar dan tentukan kesetaraan).
  • Atau, Anda dapat mengakses semua anggota secara langsung dengan mempublikasikannya.

Masalah dengan opsi pertama, adalah bahwa itu bisa menjadi BANYAK pengakses, yang (sedikit) lebih lambat dari akses variabel langsung, lebih sulit untuk dibaca, dan rumit. Masalah dengan pendekatan kedua adalah Anda benar-benar memecah enkapsulasi.

Apa yang akan menyenangkan, adalah jika kita dapat mendefinisikan fungsi eksternal yang masih bisa mendapatkan akses ke anggota pribadi kelas. Kita dapat melakukan ini dengan friendkata kunci:

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

Metode ini equal(Beer, Beer)sekarang memiliki akses langsung ke adan b's anggota pribadi (yang mungkin char *brand, float percentAlcohol, dll Ini adalah contoh yang agak dibikin, Anda akan lebih cepat menerapkan friendsebuah kelebihan beban == operator, tapi kami akan mendapatkan itu.

Beberapa hal yang perlu diperhatikan:

  • A friendBUKAN fungsi anggota kelas
  • Ini adalah fungsi biasa dengan akses khusus ke anggota pribadi kelas
  • Jangan ganti semua pengakses dan mutator dengan teman (Anda sebaiknya membuat semuanya public!)
  • Persahabatan tidak timbal balik
  • Persahabatan tidak transitif
  • Persahabatan tidak diwariskan
  • Atau, seperti yang dijelaskan dalam C ++ FAQ : "Hanya karena saya memberi Anda akses pertemanan kepada saya tidak secara otomatis memberikan anak-anak Anda akses kepada saya, tidak secara otomatis memberikan teman Anda akses kepada saya, dan tidak secara otomatis memberi saya akses kepada Anda . "

Saya hanya benar-benar menggunakan friendsketika jauh lebih sulit untuk melakukannya dengan cara lain. Sebagai contoh lain, fungsi banyak matematika vektor sering dibuat sebagai friendsakibat interoperabilitas Mat2x2, Mat3x3, Mat4x4, Vec2, Vec3, Vec4, dll Dan itu hanya jauh lebih mudah untuk menjadi teman, daripada harus menggunakan accesor mana-mana. Seperti yang ditunjukkan, friendseringkali berguna ketika diterapkan pada <<(sangat berguna untuk debugging), >>dan mungkin ==operator, tetapi juga dapat digunakan untuk hal seperti ini:

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

Seperti yang saya katakan, saya tidak friendsering menggunakannya sama sekali, tetapi kadang-kadang hanya itu yang Anda butuhkan. Semoga ini membantu!

Sesuatu yg tdk kekal
sumber
2

Sehubungan dengan operator << dan operator >> tidak ada alasan yang baik untuk membuat teman-teman operator ini. Memang benar bahwa mereka tidak boleh menjadi fungsi anggota, tetapi mereka juga tidak perlu menjadi teman.

Hal terbaik untuk dilakukan adalah membuat fungsi cetak publik (ostream &) dan membaca (istream &). Kemudian, tulis operator << dan operator >> dalam hal fungsi-fungsi tersebut. Ini memberi manfaat tambahan dengan memungkinkan Anda membuat fungsi-fungsi itu virtual, yang menyediakan serialisasi virtual.


sumber
" Sehubungan dengan operator << dan operator >> tidak ada alasan yang baik untuk membuat teman-teman operator ini. " Benar sekali. " Ini memberi manfaat tambahan dengan memungkinkan Anda membuat fungsi-fungsi itu virtual, " Jika kelas yang dimaksud dimaksudkan untuk derivasi, ya. Kalau tidak, mengapa repot-repot?
curiousguy
Saya benar-benar tidak mengerti mengapa jawaban ini diturunkan dua kali - dan bahkan tanpa penjelasan! Kasar.
curiousguy
virtual akan menambah hit perf yang bisa cukup besar dalam serialisasi
paulm
2

Saya hanya menggunakan kata kunci teman untuk melepaskan fungsi yang dilindungi. Beberapa orang akan mengatakan bahwa Anda tidak boleh menguji fungsionalitas yang dilindungi. Namun, saya menemukan alat ini sangat berguna ketika menambahkan fungsionalitas baru.

Namun, saya tidak menggunakan kata kunci secara langsung di deklarasi kelas, alih-alih saya menggunakan hack-template yang bagus untuk mencapai ini:

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

Ini memungkinkan saya untuk melakukan hal berikut:

friendMe(this, someClassInstance).someProtectedFunction();

Bekerja pada GCC dan MSVC minimal.

larsmoa
sumber
2

Dalam C ++ "teman" kata kunci berguna dalam Overloading dan Membuat Jembatan Operator.

1.) Kata kunci teman dalam overloading operator:
Contoh untuk overloading operator adalah: Katakanlah kita memiliki kelas "Point" yang memiliki dua variabel float
"x" (untuk koordinat x) dan "y" (untuk koordinat y). Sekarang kita harus membebani "<<"(operator ekstraksi) sehingga jika kita memanggil "cout << pointobj"maka akan mencetak koordinat x dan y (di mana pointobj adalah objek dari class Point). Untuk melakukan ini kami memiliki dua opsi:

   1.Overload "operator << ()" berfungsi di kelas "ostream".
   2.Overload "operator << ()" berfungsi di kelas "Point".
Opsi Sekarang First tidak bagus karena jika kita perlu membebani lagi operator ini untuk beberapa kelas yang berbeda maka kita harus kembali melakukan perubahan di kelas "ostream".
Itu sebabnya pilihan kedua adalah yang terbaik. Sekarang kompiler dapat memanggil "operator <<()"fungsi:

   1.Menggunakan objek ostream cout.As: cout.operator << (Pointobj) (membentuk kelas ostream). 
2. Panggilan tanpa objek. Sebagai: operator << (cout, Pointobj) (dari kelas Point).

Karena kami telah menerapkan overloading di kelas Point. Jadi untuk memanggil fungsi ini tanpa objek, kita harus menambahkan "friend"kata kunci karena kita dapat memanggil fungsi teman tanpa objek. Sekarang deklarasi fungsi akan Sebagai:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"

2.) Kata kunci teman dalam membuat jembatan:
Misalkan kita harus membuat fungsi di mana kita harus mengakses anggota pribadi dari dua kelas atau lebih (umumnya disebut sebagai "jembatan"). Cara melakukan ini:
Untuk mengakses anggota pribadi dari suatu kelas, ia haruslah anggota dari kelas itu. Sekarang untuk mengakses anggota pribadi dari kelas lain, setiap kelas harus mendeklarasikan fungsi itu sebagai fungsi teman. Sebagai contoh: Misalkan ada dua kelas A dan B. Suatu fungsi "funcBridge()"ingin mengakses anggota pribadi dari kedua kelas. Maka kedua kelas harus menyatakan "funcBridge()"sebagai:
friend return_type funcBridge(A &a_obj, B & b_obj);

Saya pikir ini akan membantu untuk memahami kata kunci teman.

jadnap
sumber
2

Seperti referensi untuk pernyataan teman mengatakan:

Deklarasi teman muncul di badan kelas dan memberikan fungsi atau akses kelas lain ke anggota kelas yang dilindungi dan pribadi di mana deklarasi teman tersebut muncul.

Jadi hanya sebagai pengingat, ada kesalahan teknis dalam beberapa jawaban yang mengatakan bahwa friendhanya dapat mengunjungi anggota yang dilindungi .

lixunhuan
sumber
1

Contoh pohon adalah contoh yang cukup bagus: Memiliki objek diimplementasikan dalam beberapa kelas yang berbeda tanpa memiliki hubungan warisan.

Mungkin Anda juga bisa membutuhkannya agar konstruktor terlindungi dan memaksa orang untuk menggunakan pabrik "teman" Anda.

... Oke, terus terang Anda bisa hidup tanpanya.

fulmicoton
sumber
1

Untuk melakukan TDD berkali-kali saya menggunakan kata kunci 'teman' di C ++.
Bisakah seorang teman mengetahui segalanya tentang saya?

Tidak, ini hanya persahabatan satu arah: `(

Roo
sumber
1

Satu contoh spesifik di mana saya gunakan friendadalah ketika membuat kelas Singleton . Kata friendkunci memungkinkan saya membuat fungsi accessor, yang lebih ringkas daripada selalu memiliki metode "GetInstance ()" di kelas.

/////////////////////////
// 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;
}
Matt Dillard
sumber
Ini mungkin masalah selera, tapi saya pikir menyimpan beberapa penekanan tombol tidak membenarkan penggunaan teman di sini. GetMySingleton () harus menjadi metode statis kelas.
Gorpik
C-tor pribadi akan melarang fungsi non-teman untuk membuat Instansiate MySingleton, sehingga kata kunci teman diperlukan di sini.
JBRWilkinson
@ Gorpik " Ini mungkin masalah selera, tapi saya rasa menyimpan beberapa penekanan tombol tidak membenarkan penggunaan teman di sini. " Pokoknya, friendtidak tidak perlu "pembenaran" tertentu, saat menambahkan fungsi anggota tidak.
curiousguy
Lajang dianggap sebagai praktik yang buruk (Google "singleton berbahaya" dan Anda akan mendapatkan banyak hasil seperti ini . Saya tidak berpikir menggunakan fitur untuk mengimplementasikan antipattern dapat dianggap sebagai penggunaan fitur itu dengan baik.
weberc2
1

Fungsi dan kelas Teman menyediakan akses langsung ke anggota kelas pribadi dan terlindungi untuk menghindari melanggar enkapsulasi dalam kasus umum. Sebagian besar penggunaan menggunakan ostream: kami ingin dapat mengetik:

Point p;
cout << p;

Namun, ini mungkin memerlukan akses ke data pribadi Point, jadi kami mendefinisikan operator kelebihan beban

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

Namun, ada implikasi enkapsulasi yang jelas. Pertama, sekarang kelas atau fungsi teman memiliki akses penuh ke SEMUA anggota kelas, bahkan yang tidak berkaitan dengan kebutuhannya. Kedua, implementasi kelas dan teman sekarang terjerat ke titik di mana perubahan internal di kelas dapat menghancurkan teman.

Jika Anda melihat teman sebagai perpanjangan kelas, maka ini bukan masalah, secara logis. Tetapi, dalam hal itu, mengapa perlu untuk mempelopori teman sejak awal.

Untuk mencapai hal yang sama dengan tujuan 'teman' untuk dicapai, tetapi tanpa melanggar enkapsulasi, seseorang dapat melakukan ini:

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

Enkapsulasi tidak rusak, kelas B tidak memiliki akses ke implementasi internal di A, namun hasilnya sama seperti jika kami telah menyatakan B sebagai teman A. Kompiler akan mengoptimalkan panggilan fungsi secara otomatis, jadi ini akan menghasilkan sama instruksi sebagai akses langsung.

Saya pikir menggunakan 'teman' hanyalah jalan pintas dengan manfaat yang bisa diperdebatkan, tetapi biaya pasti.

Lubo Antonov
sumber
1

Ini mungkin bukan situasi kasus penggunaan aktual tetapi dapat membantu menggambarkan penggunaan teman di antara kelas.

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

Kelas Anggota

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

Fasilitas

class Amenity{};

Jika Anda melihat hubungan kelas-kelas ini di sini; ClubHouse memiliki beragam jenis keanggotaan dan akses keanggotaan. Semua Anggota berasal dari kelas super atau basis karena mereka semua berbagi ID dan tipe yang disebutkan yang umum dan kelas luar dapat mengakses ID dan Jenis mereka melalui fungsi akses yang ditemukan di kelas dasar.

Namun melalui hierarki Anggota dan kelas turunannya seperti ini dan hubungannya dengan kelas ClubHouse, satu-satunya kelas turunan yang memiliki "hak istimewa" adalah kelas VIPMember. Kelas dasar dan 2 kelas turunan lainnya tidak dapat mengakses metode ClubHouse joinVIPEvent (), namun kelas Anggota VIP memiliki hak istimewa itu seolah-olah memiliki akses penuh ke acara tersebut.

Jadi dengan VIPMember dan ClubHouse itu adalah jalan akses dua arah di mana Kelas Anggota lainnya terbatas.

Francis Cugler
sumber
0

Ketika mengimplementasikan algoritma pohon untuk kelas, kode kerangka kerja yang diberikan oleh prof kepada kami memiliki kelas pohon sebagai teman dari kelas simpul.

Itu tidak benar-benar ada gunanya, selain membiarkan Anda mengakses variabel anggota tanpa menggunakan fungsi pengaturan.

Ryan Fox
sumber
0

Anda dapat menggunakan pertemanan ketika kelas yang berbeda (tidak mewarisi satu dari yang lain) menggunakan anggota pribadi atau kelas yang dilindungi.

Kasus penggunaan umum dari fungsi teman adalah operasi yang dilakukan antara dua kelas yang berbeda mengakses anggota pribadi atau yang dilindungi dari keduanya.

dari http://www.cplusplus.com/doc/tutorial/inheritance/ .

Anda dapat melihat contoh ini di mana metode non-anggota mengakses anggota pribadi kelas. Metode ini harus dinyatakan dalam kelas ini sebagai teman kelas.

// 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;
}
Kiriloff
sumber
0

Mungkin saya melewatkan sesuatu dari jawaban di atas tetapi konsep penting lainnya dalam enkapsulasi adalah menyembunyikan implementasi. Mengurangi akses ke anggota data pribadi (detail implementasi suatu kelas) memungkinkan modifikasi kode yang lebih mudah nanti. Jika seorang teman langsung mengakses data pribadi, setiap perubahan pada bidang data implementasi (data pribadi), pecahkan kode yang mengakses data itu. Menggunakan metode akses sebagian besar menghilangkan ini. Cukup penting menurut saya.

peterdcasey
sumber
-1

Anda dapat mematuhi prinsip-prinsip OOP yang paling ketat dan paling murni dan memastikan bahwa tidak ada anggota data untuk kelas apa pun yang memiliki pengakses sehingga semua objek harus menjadi satu-satunya yang dapat mengetahui tentang data mereka dengan satu-satunya cara untuk bertindak atas mereka adalah melalui pesan tidak langsung , yaitu, metode.

Tetapi bahkan C # memiliki kata kunci visibilitas internal dan Java memiliki aksesibilitas tingkat paket default untuk beberapa hal. C ++ benar-benar mendekati ideal OOP dengan meminimalkan kompromi visibilitas ke dalam kelas dengan menentukan dengan tepat kelas mana dan hanya kelas lain yang bisa melihatnya.

Saya tidak benar-benar menggunakan C ++ tetapi jika C # memiliki teman saya akan melakukan itu daripada pengubah internal perakitan-global , yang saya benar-benar menggunakan banyak. Itu tidak benar-benar memecahkan inkapsulasi, karena unit penyebaran di .NET adalah sebuah perakitan.

Tapi kemudian ada atribut InternalsVisibleTo (otherAssembly) yang bertindak seperti mekanisme teman lintas-perakitan . Microsoft menggunakan ini untuk rakitan desainer visual .

Mark Cidade
sumber
-1

Teman juga berguna untuk panggilan balik. Anda bisa menerapkan panggilan balik sebagai metode statis

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

di mana callbackpanggilan secara localCallbackinternal, dan clientDatamemiliki instance Anda di dalamnya. Menurutku,

atau...

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

Apa yang memungkinkan adalah teman didefinisikan murni dalam cpp sebagai fungsi c-style, dan tidak mengacaukan kelas.

Demikian pula, pola yang sering saya lihat adalah menempatkan semua anggota kelas yang benar - benar pribadi ke dalam kelas lain, yang dideklarasikan di header, didefinisikan dalam cpp, dan berteman. Ini memungkinkan coder untuk menyembunyikan banyak kerumitan dan kerja internal kelas dari pengguna header.

Di tajuk:

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

Di cpp,

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

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

Menjadi lebih mudah untuk menyembunyikan hal-hal yang tidak perlu dilihat hilir dengan cara ini.

shash
sumber
1
Bukankah antarmuka akan menjadi cara yang lebih bersih untuk mencapai ini? Apa yang menghentikan seseorang mencari MyFooPrivate.h?
JBRWilkinson
1
Nah, jika Anda menggunakan pribadi dan publik untuk menyimpan rahasia, Anda akan dikalahkan dengan mudah. Dengan "bersembunyi", maksud saya, pengguna MyFoo tidak benar-benar perlu melihat anggota pribadi. Selain itu, berguna untuk menjaga kompatibilitas ABI. Jika Anda membuat _private pointer, implementasi pribadi dapat berubah sebanyak yang Anda inginkan, tanpa menyentuh antarmuka publik, sehingga menjaga kompatibilitas ABI.
shash
Anda mengacu pada idiom PIMPL; tujuan yang bukan enkapsulasi tambahan seperti yang Anda katakan, tetapi untuk memindahkan detail implementasi dari header sehingga mengubah detail implementasi tidak memaksa kompilasi ulang kode klien. Selanjutnya, tidak perlu menggunakan teman untuk menerapkan idiom ini.
weberc2
Baiklah. Tujuan utamanya adalah untuk memindahkan detail implementasi. Teman di sana berguna untuk menangani anggota pribadi di dalam kelas publik dari pribadi atau sebaliknya.
shash