C ++ neden miras alınan arkadaşlığa izin vermiyor?


95

Arkadaşlık neden en azından isteğe bağlı olarak C ++ 'da miras alınamaz? Geçişkenliğin ve dönüşlülüğün bariz nedenlerden dolayı yasaklandığını anlıyorum (bunu sadece basit SSS alıntı cevaplarından kaçınmak için söylüyorum), ancak virtual friend class Foo;bulmacalar doğrultusunda bir şeylerin olmaması beni şaşırtıyor. Bu kararın arkasındaki tarihsel arka planı bilen var mı? Arkadaşlık gerçekten de, o zamandan beri birkaç belirsiz saygın kullanıma giren sınırlı bir hack miydi?

Açıklama Düzenleme: Ben aşağıdaki senaryoda, bahsediyorum değil A'nın çocukları ya B'ye veya B ve hem de çocuklar maruz kalmaktadır. Ayrıca isteğe bağlı olarak arkadaş işlevlerinin geçersiz kılmalarına vb. Erişim izni vermeyi de hayal edebiliyorum.

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

Kabul cevap: olarak Loki devletler , etkisi dolayısıyla sıkı yoktur, az ya da çok friendede baz sınıflarında korumalı vekil fonksiyonları yaparak simüle edilebilir ihtiyacı bir sınıf veya sanal yöntem heirarchy için dostluk verilmesi. Standart vekillere duyulan ihtiyaçtan hoşlanmıyorum (ki bu dost taban etkili bir şekilde oluyor), ancak bunun çoğu zaman kötüye kullanılması daha muhtemel olan bir dil mekanizmasına göre tercih edildiğini düşünüyorum. Muhtemelen Stroupstrup'un The Design and Evolution of C ++ adlı kitabını satın alıp okumanın zamanı geldi , ki burada yeterince insan bu tür sorulara daha iyi bir bakış açısı kazandırmak için tavsiye etti ...

Yanıtlar:


94

Çünkü yazabilirim Foove onun arkadaşı Bar(böylece bir güven ilişkisi var).

Ama türetilen dersleri yazan insanlara güveniyor muyum Bar?
Pek sayılmaz. Yani arkadaşlığı miras almamalılar.

Bir sınıfın iç temsilindeki herhangi bir değişiklik, bu temsile bağlı olan herhangi bir şeyin değiştirilmesini gerektirecektir. Bu nedenle, bir sınıfın tüm üyeleri ve ayrıca sınıfın tüm arkadaşları değişiklik gerektirecektir.

İç temsili Bu nedenle, eğer Foodaha sonra modifiye edilmiş Bar(dostluğu sıkıca bağlar, çünkü, aynı zamanda değiştirilmesi gerekir Bariçin Foo). Arkadaşlık miras alınırsa, bundan türetilen tüm sınıflar Barda sıkı bir şekilde bağlanır Foove bu nedenle, eğer Fooiç temsili değiştirilirse değişiklik gerektirir . Ancak türetilmiş türler hakkında hiçbir bilgim yok (ben de olmamalıyım. Farklı şirketler tarafından geliştirilebilirler vb.). Bu nedenle Foo, bunu yapmak kod tabanında değişikliklere neden olacağından (türetilen tüm sınıfı değiştiremediğim için Bar) değiştiremem .

Bu nedenle, eğer arkadaşlık miras alınmışsa, yanlışlıkla bir sınıfı değiştirme yeteneğine bir kısıtlama getirmiş olursunuz. Genel API kavramını temelde yararsız hale getirdiğiniz için bu istenmeyen bir durumdur.

Not: Bir alt öğesi kullanarak Barerişebilir , sadece yöntemi korumalı yapın. Daha sonra alt öğesi, a üst sınıfını arayarak erişebilir.FooBarBarBarFoo

İstediğiniz bu mu?

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

4
Bingo. Her şey bir sınıfın iç kısımlarını değiştirerek verebileceğiniz hasarı sınırlamakla ilgilidir.
j_random_hacker

Açıkçası, bunu gerçekten düşündüğüm durum, bir ara seviyenin, temelde yatan sınırlı erişimli sınıfa sarmalayıcı yöntemleri sunarak sınıfları dışlamak için sınırlı bir arayüz olarak hareket ettiği Avukat-Müşteri modelidir. Bir arayüzün diğer sınıfların tüm çocukları için kesin bir sınıf yerine kullanılabilir olduğunu söylemek, şu anda sistemden çok daha yararlı olacaktır.
Jeff

@Jeff: Dahili temsili bir sınıfın tüm çocuklarına göstermek, kodun değiştirilemez hale gelmesine neden olur (aynı zamanda, iç üyelere erişmek isteyen herhangi birinin gerçekten bir Bar olmasa bile yalnızca Bar'dan miras alması gerektiğinden, kapsüllemeyi de bozar. ).
Martin York

@Martin: Doğru, bu şemada arkadaşlık tabanı arkadaşlık sınıfına ulaşmak için kullanılabilir, bu çoğu durumda (çoğu değilse) basit kapsülleme ihlali olabilir. Bununla birlikte, arkadaşlı tabanın soyut bir sınıf olduğu durumlarda, türetilmiş herhangi bir sınıf, kendi karşılıklı arayüzünü uygulamaya zorlanacaktır. Bu senaryodaki bir 'sahtekar' sınıfının, iddia edilen rolünü doğru bir şekilde yerine getirmeye çalışmadığı takdirde, kapsüllemeyi bozuyor mu yoksa bir arayüz sözleşmesini ihlal ediyor mu olarak değerlendirileceğinden emin değilim.
Jeff

@Martin: Doğru, istediğim ve bazen zaten kullandığım etki bu, burada A aslında bazı kısıtlı erişim sınıfına Z'ye karşılıklı dostane bir arayüz. Normal Avukat-Müşteri İdiom'unun ortak şikayeti arayüz sınıfı gibi görünüyor A'nın arama sarmalayıcılarını Z'ye sabitlemesi gerekir ve alt sınıflara erişimi genişletmek için, A'nın ortak metninin B gibi her temel sınıfta esas olarak kopyalanması gerekir.Bir arayüz normalde bir modülün hangi işlevselliği sunmak istediğini, diğerlerinde hangi işlevselliği değil. kendini kullanmak istiyor.
Jeff

49

Arkadaşlık neden en azından isteğe bağlı olarak C ++ 'da miras alınamaz?

İlk sorunuzun cevabının şu soruda olduğunu düşünüyorum: "Babanızın arkadaşlarının sizin özel eşyalarınıza erişimi var mı?"


37
Dürüst olmak gerekirse, bu soru babanız hakkında rahatsız edici soruları gündeme getiriyor. . .
iheanyi

3
Bu cevabın amacı nedir? en iyi ihtimalle şüpheli, ancak muhtemelen hafif yürekli bir yorum
DeveloperChris

11

Arkadaşlı bir sınıf, arkadaşını erişimci işlevleri yoluyla ifşa edebilir ve sonra bunlar aracılığıyla erişim verebilir.

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

Bu, isteğe bağlı geçişkenliğe göre daha hassas kontrol sağlar. Örneğin, get_casholabilir protectedveya çalışma zamanı sınırlı bir erişim protokolü getirebilir.


@Hector: Yeniden düzenleme için oylama! ;)
Alexander Shukaev

8

C ++ Standard, bölüm 11.4 / 8

Arkadaşlık ne miras alınır ne de geçişlidir.

Eğer arkadaşlık miras alınacaksa, arkadaş olması amaçlanmayan bir sınıf aniden sınıf içi derslerinize erişebilir ve bu da kapsüllemeyi ihlal eder.


2
Q "arkadaşlar" A diyelim ve B A'dan türetilmiştir. Eğer B, A'dan gelen arkadaşlığı devralırsa, o zaman B bir A tipi olduğu için, teknik olarak Q'nun ayrıcalıklarına erişimi olan bir A'dır. Yani bu soruya herhangi bir pratik sebeple cevap vermiyor.
mdenton8

2

Çünkü bu gereksiz.

friendAnahtar kelimenin kullanımı şüphelidir. Birleştirme açısından bu en kötü ilişkidir (kalıtım ve kompozisyonun çok ötesinde).

Bir sınıfın içindekilerle ilgili herhangi bir değişikliğin bu sınıftaki arkadaşlarını etkileme riski vardır ... Gerçekten bilinmeyen sayıda arkadaş mı istiyorsun? Onlardan miras alan kişiler de arkadaş olsalar onları listeleyemezsiniz ve her seferinde müşterilerinizin kodunu kırma riskiyle karşı karşıya kalırsınız, kesinlikle bu arzu edilmez.

Ev ödevi / evcil hayvan projeleri için bağımlılığın genellikle çok uzakta bir düşünce olduğunu özgürce kabul ediyorum. Küçük boyutlu projelerde önemli değil. Ancak aynı proje üzerinde birkaç kişi çalışır çalışmaz ve bu düzinelerce satıra ulaştığında, değişikliklerin etkisini sınırlamanız gerekir.

Bu çok basit bir kural getiriyor:

Bir sınıfın iç bileşenlerini değiştirmek yalnızca sınıfın kendisini etkilemelidir

Tabii ki, muhtemelen arkadaşlarını etkileyeceksin, ancak burada iki durum var:

  • arkadaşsız işlev: muhtemelen daha çok üye işlevi zaten (bence std::ostream& operator<<(...)burada, yalnızca dil kurallarının tesadüfen üye olmadığını düşünüyorum
  • arkadaş sınıfı? gerçek sınıflarda arkadaş derslerine ihtiyacınız yok.

Basit yöntemin kullanılmasını tavsiye ederim:

class Example;

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

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

Bu basit Keykalıp, bir arkadaşınızı (bir şekilde) kendi içlerinize erişmesine izin vermeden ilan etmenize ve böylece onu değişikliklerden izole etmenize olanak tanır. Dahası, bu arkadaşın gerekirse anahtarını mütevellilere (çocuklar gibi) ödünç vermesine izin verir.


0

Bir tahmin: Bir sınıf başka bir sınıfı / işlevi arkadaş olarak ilan ederse, bunun nedeni ikinci varlığın birinciye ayrıcalıklı erişime ihtiyaç duymasıdır. İkinci varlığa, birinciden türetilen keyfi sayıda sınıfa ayrıcalıklı erişim vermenin ne faydası var?


2
Eğer bir A sınıfı, B'ye ve onun soyundan gelenlere arkadaşlık kazandırmak isterse, eklenen her alt sınıf için arayüzünü güncellemekten veya B'yi geçişli şablon yazmaya zorlamaktan kaçınabilir ki bu da arkadaşlığın ilk noktasında arkadaşlığın yarısıdır.
Jeff

@Jeff: Ah, demek istediğin anlamı yanlış anladım. Bundan Bdevralınan tüm sınıflara erişim hakkına sahip olmayı kastettiğinizi varsaymıştım A...
Oliver Charlesworth

0

Türetilmiş bir sınıf, yalnızca tabanın 'üyesi' olan bir şeyi miras alabilir. Bir arkadaş beyanı, yeni arkadaşlık sınıfının bir üyesi değildir .

$ 11.4 / 1- "... Bir arkadaşın adı sınıf kapsamında değildir ve arkadaş başka bir sınıfın üyesi olmadığı sürece üye erişim operatörleri (5.2.5) ile çağrılmaz."

$ 11.4 - "Ayrıca, arkadaş sınıfının temel cümlesi üye bildirimlerinin bir parçası olmadığından, arkadaş sınıfının temel cümlesi, arkadaşlık sağlayan sınıftan özel ve korumalı üyelerin adlarına erişemez."

ve Ötesi

$ 10.3 / 7- "[Not: sanal belirteç üyeliği ifade eder, bu nedenle sanal bir işlev üye olmayan (7.1.2) bir işlev olamaz. Bir sanal işlev, statik bir üye olamaz, çünkü bir sanal işlev hangi işlevin çağrılacağını belirleme. Bir sınıfta bildirilen bir sanal işlev, başka bir sınıfta bir arkadaş olarak ilan edilebilir.] "

'Arkadaş' ilk etapta temel sınıfın bir üyesi olmadığına göre, türetilmiş sınıf tarafından nasıl miras alınabilir?


Arkadaşlık, üyeler gibi bildirimler yoluyla verilse de, diğer sınıfların 'gerçek' üyeler üzerindeki görünürlük sınıflandırmalarını esasen görmezden gelebileceği bildirimler kadar gerçekten üye değildir. Alıntı yaptığınız spesifikasyon bölümleri, dilin bu nüanslarla ilgili nasıl işlediğini açıklarken ve davranışları kendi kendine tutarlı terminolojide çerçevelerken, şeyler farklı şekilde hazırlanmış olabilir ve maalesef yukarıdaki hiçbir şey mantığın özüne inmez.
Jeff

0

Bir sınıftaki Friend işlevi, extern özelliğini işleve atar. ie extern, işlevin sınıf dışında bir yerde bildirildiği ve tanımlandığı anlamına gelir.

Bu nedenle, arkadaş işlevinin bir sınıfın üyesi olmadığı anlamına gelir. Dolayısıyla miras, yalnızca bir sınıfın özelliklerini miras almanıza izin verir, harici şeyleri değil. Ayrıca, arkadaş işlevleri için kalıtıma izin veriliyorsa, üçüncü taraf bir sınıf miras alır.


0

Arkadaş, kapsayıcı için stil arabirimi gibi kalıtım açısından iyidir Ama benim için, ilk söylediği gibi, C ++ yayılabilir kalıtımdan yoksundur

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

Benim için C ++ sorunu, herhangi bir şey için herhangi bir yerden tüm erişimi kontrol etmek için çok iyi bir ayrıntı düzeyinin olmamasıdır:

arkadaş Şey, Thing'in tüm çocuğuna erişim izni vermek için arkadaş Şey olabilir. *

Ve dahası, arkadaş için [alan adı] Thing. * İçin kesin erişim izni vermek, arkadaş için özel adlandırılmış alan aracılığıyla Konteyner sınıfındadır.

Tamam rüyayı durdur. Ama şimdi, arkadaşın ilginç bir kullanımını biliyorsun.

Başka bir sırayla, tüm sınıfların kendisiyle dost olduğunu da ilginç bulabilirsiniz. Diğer bir
deyişle , bir sınıf örneği aynı ada sahip başka bir örneğin tüm üyelerini kısıtlama olmaksızın çağırabilir :

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

0

Basit mantık: 'Bir arkadaşım Jane var. Dün arkadaş olmamız bütün arkadaşlarını benim yapmaz. '

Yine de bu bireysel arkadaşlıkları onaylamam gerekiyor ve güven seviyesi buna göre olacak.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.