Pertanyaan Mengapa C ++ tidak mengizinkan persahabatan yang diwariskan?


Mengapa pertemanan tidak setidaknya diwariskan secara opsional di C ++? Saya memahami transitivitas dan refleksivitas yang dilarang untuk alasan yang jelas (saya mengatakan ini hanya untuk mundur dari jawaban kutipan FAQ sederhana), tetapi kurangnya sesuatu di sepanjang garis virtual friend class Foo; tantang saya. Adakah yang tahu latar belakang sejarah di balik keputusan ini? Apakah pertemanan benar-benar hanya hack terbatas yang sejak itu menemukan jalannya menjadi beberapa penggunaan terhormat yang tidak jelas?

Edit untuk klarifikasi: Saya sedang berbicara tentang skenario berikut, tidak di mana anak-anak A terpapar ke B atau ke B dan anak-anaknya. Saya juga bisa membayangkan opsional memberikan akses untuk menimpa fungsi teman, dll.

class A {
  int x;
  friend class B;
};

class B {
  // OK as per friend declaration above.
  void foo(A& a, int n) { a.x = n; }
};

class D : public B { /* can't get in A w/o 'friend class D' declaration. */ };

Jawaban yang diterima: sebagai Loki menyatakan, efeknya dapat disimulasikan lebih kurang dengan membuat fungsi proxy yang dilindungi dalam kelas dasar yang bersahabat, jadi tidak ada yang ketat perlu untuk memberikan persahabatan ke kelas atau metode virtual heirarchy. Saya tidak suka perlunya proksi boilerplate (yang menjadi basis yang bersahabat), tetapi saya kira ini lebih disukai daripada mekanisme bahasa yang kemungkinan besar akan disalahgunakan sebagian besar waktu. Saya pikir itu mungkin waktu saya membeli dan membaca Stroupstrup Desain dan Evolusi C ++, yang telah saya lihat cukup banyak orang di sini sarankan, untuk mendapatkan wawasan yang lebih baik untuk jenis pertanyaan ini ...


75


asal


Jawaban:


Karena saya bisa menulis Foo dan temannya Bar (Dengan demikian ada hubungan kepercayaan).

Tapi apakah saya percaya orang-orang yang menulis kelas yang berasal dari Bar?
Tidak juga. Jadi mereka seharusnya tidak mewarisi pertemanan.

Setiap perubahan dalam representasi internal suatu kelas akan membutuhkan modifikasi pada apa pun yang bergantung pada representasi itu. Jadi semua anggota kelas dan juga semua teman kelas akan membutuhkan modifikasi.

Oleh karena itu jika representasi internal Foo dimodifikasi kemudian Bar juga harus dimodifikasi (karena persahabatan mengikat erat Bar untuk Foo). Jika persahabatan diwariskan maka semua kelas berasal Bar juga akan terikat erat Foo dan dengan demikian membutuhkan modifikasi jika FooRepresentasi internal diubah. Tapi saya tidak memiliki pengetahuan tentang tipe turunan (atau seharusnya I. Mereka bahkan mungkin dikembangkan oleh perusahaan yang berbeda dll). Dengan demikian saya tidak akan bisa berubah Foo dengan melakukan hal itu akan memperkenalkan perubahan yang melanggar ke dalam basis kode (karena saya tidak dapat memodifikasi semua kelas yang berasal dari Bar).

Jadi jika persahabatan diwariskan, Anda secara tidak sengaja memperkenalkan pembatasan pada kemampuan untuk memodifikasi kelas. Ini tidak diinginkan karena Anda pada dasarnya menjadikan tidak bergunanya konsep API publik.

Catatan: Anak dari Bar dapat mengakses Foo dengan menggunakan Bar, cukup buat metode di Bar terlindung. Kemudian anak dari Bar dapat mengakses a Foo dengan menelepon melalui kelas induknya.

Apa ini yang kau inginkan?

class A
{
    int x;
    friend class B;
};

class B
{
    protected:
       // Now children of B can access foo
       void foo(A& a, int n) { a.x = n; }
};

class D : public B
{
    public:
        foo(A& a, int n)
        {
            B::foo(a, n + 5);
        }
};

73



Mengapa pertemanan tidak setidaknya diwariskan secara opsional di C ++?

Saya pikir jawaban untuk pertanyaan pertama Anda ada dalam pertanyaan ini: "Apakah teman ayahmu memiliki akses ke kemaluanmu?"


33



Sebuah kelas yang bersahabat dapat mengekspos temannya melalui fungsi-fungsi accessor, dan kemudian memberikan akses melalui itu.

class stingy {
    int pennies;
    friend class hot_girl;
};

class hot_girl {
public:
    stingy *bf;

    int &get_cash( stingy &x = *bf ) { return x.pennies; }
};

class moocher {
public: // moocher can access stingy's pennies despite not being a friend
    int &get_cash( hot_girl &x ) { return x.get_cash(); }
};

Ini memungkinkan kontrol yang lebih baik daripada transitivitas opsional. Sebagai contoh, get_cash mungkin protected atau dapat menegakkan protokol akses runtime-terbatas.


7



C ++ Standard, bagian 11.4 / 8

Persahabatan tidak diwariskan atau transitif.

Jika persahabatan akan diwariskan, maka kelas yang tidak dimaksudkan untuk menjadi teman akan tiba-tiba memiliki akses ke internal kelas Anda dan yang melanggar enkapsulasi.


7



Karena itu tidak perlu.

Penggunaan dari friend kata kunci itu sendiri mencurigakan. Dalam istilah kopling, itu adalah hubungan terburuk (jalan di depan pewarisan dan komposisi).

Perubahan apa pun ke internal kelas memiliki risiko memengaruhi teman-teman kelas ini ... apakah Anda benar-benar menginginkan jumlah teman yang tidak dikenal? Anda bahkan tidak akan dapat mendaftar mereka jika mereka yang mewarisi dari mereka bisa menjadi teman juga, dan Anda akan menghadapi risiko melanggar kode klien Anda setiap kali, tentunya ini tidak diinginkan.

Saya dengan bebas mengakui bahwa untuk pekerjaan rumah / proyek, ketergantungan sering merupakan pertimbangan yang jauh. Pada proyek ukuran kecil tidak masalah. Tetapi segera setelah beberapa orang bekerja pada proyek yang sama dan ini berkembang menjadi lusinan ribuan garis, Anda perlu membatasi dampak perubahan.

Ini membawa aturan yang sangat sederhana:

Mengubah internal kelas hanya akan mempengaruhi kelas itu sendiri

Tentu saja, Anda mungkin akan memengaruhi teman-temannya, tetapi ada dua kasus di sini:

  • fungsi bebas teman: mungkin lebih dari fungsi anggota pula (saya pikir std::ostream& operator<<(...) di sini, yang bukan anggota murni karena aturan bahasa yang tidak disengaja
  • kelas teman? Anda tidak perlu kelas teman di kelas sungguhan.

Saya akan merekomendasikan penggunaan metode sederhana:

class Example;

class ExampleKey { friend class Example; ExampleKey(); };

class Restricted
{
public:
  void forExampleOnly(int,int,ExampleKey const&);
};

Sederhana ini Key pattern memungkinkan Anda untuk mendeklarasikan teman (dengan cara) tanpa benar-benar memberikannya akses ke internal Anda, sehingga mengisolasinya dari perubahan. Lebih lanjut, hal ini memungkinkan teman ini untuk meminjamkan kunci kepada wali (seperti anak-anak) jika diperlukan.


2



Tebakan: Jika kelas menyatakan beberapa kelas / fungsi lain sebagai teman, itu karena entitas kedua membutuhkan akses istimewa ke yang pertama. Apa gunanya memberikan kepada entitas kedua akses istimewa ke sejumlah kelas acak yang berasal dari yang pertama?


0



Kelas turunan hanya dapat mewarisi sesuatu, yang merupakan 'anggota' dari basis. Deklarasi teman adalah tidak anggota kelas yang berteman.

$ 11.4 / 1- "... Nama teman adalah   tidak dalam ruang lingkup kelas, dan   teman tidak dipanggil dengan anggota   operator akses (5.2.5) kecuali jika memang demikian   anggota kelas lain. "

$ 11,4 - "Juga, karena basis-klausa   kelas teman bukan bagian dari itu   deklarasi anggota, klausa dasar   kelas teman tidak dapat mengakses   nama pribadi dan dilindungi   anggota dari pemberian kelas   persahabatan."

dan selanjutnya

$ 10.3 / 7- "[Catatan: specifier virtual   menyiratkan keanggotaan, jadi virtual   fungsi tidak dapat menjadi nonanggota (7.1.2)   fungsi. Juga tidak bisa berfungsi virtual   menjadi anggota statis, sejak virtual   panggilan fungsi bergantung pada spesifik   objek untuk menentukan fungsi mana   untuk memohon. Sebuah fungsi virtual dideklarasikan   dalam satu kelas dapat dinyatakan sebagai teman   di kelas lain. ] "

Karena 'teman' bukan anggota kelas dasar di tempat pertama, bagaimana bisa diwarisi oleh kelas turunan?


0



Fungsi teman di kelas menetapkan properti eksternal ke fungsi. yaitu extern berarti bahwa fungsi telah dideklarasikan dan didefinisikan di suatu tempat di luar kelas.

Oleh karena itu, artinya fungsi teman bukanlah anggota kelas. Jadi warisan hanya memungkinkan Anda untuk mewarisi sifat-sifat kelas bukan hal-hal eksternal. Dan juga jika warisan diperbolehkan untuk fungsi teman, maka kelas pihak ketiga yang mewarisi.


0



Teman baik dalam hal warisan seperti antarmuka gaya untuk wadah Tapi bagi saya, seperti yang dikatakan pertama, C ++ tidak memiliki warisan yang dapat disebarluaskan

class Thing;

//an interface for Thing container's
struct IThing {
   friend Thing;
   protected:
       int IThing_getData() = 0;
};

//container for thing's
struct MyContainer : public IThing {
    protected: //here is reserved access to Thing
         int IThing_getData() override {...}
};

struct Thing {
    void setYourContainer(IThing* aContainerOfThings) {
        //access to unique function in protected area 
        aContainerOfThings->IThing_getData(); //authorized access
    }
};

struct ChildThing : public Thing {
    void doTest() {
        //here the lack of granularity, you cannot access to the container.
        //to use the container, you must implement all 
        //function in the Thing class
        aContainerOfThings->IThing_getData(); //forbidden access
    }
};

Bagi saya masalah C ++ adalah kurangnya perincian yang sangat baik kontrol semua akses dari mana saja untuk apa pun:

teman Thing bisa menjadi teman Thing. * untuk memberikan akses ke semua anak Thing

Dan lebih lagi, teman [daerah bernama] Hal. * Untuk memberikan akses yang tepat berada di kelas Penampung melalui area khusus yang ditentukan untuk teman.

Ok hentikan mimpi itu. Tapi sekarang, Anda tahu penggunaan teman yang menarik.

Dalam urutan lain, Anda juga dapat menemukan hal yang menarik untuk diketahui semua kelas ramah dengan diri sendiri. Dengan kata lain, instance kelas dapat memanggil semua
anggota contoh lain dengan nama yang sama tanpa batasan:

class Object {
     private:
         void test() {}
     protected:
         void callAnotherTest(Object* anotherObject) {
             //private, but yes you can call test() from 
             //another object instance
             anotherObject)->test(); 
         }
};

0