Dlaczego przyjaźń nie jest przynajmniej opcjonalnie dziedziczona w C ++? Rozumiem, że przechodniość i refleksyjność są zabronione z oczywistych powodów (mówię to tylko po to, by odrzucić proste odpowiedzi na FAQ), ale brak czegoś w rodzaju virtual friend class Foo;
zagadek mnie zaskakuje. Czy ktoś zna historyczne tło tej decyzji? Czy przyjaźń naprawdę była tylko ograniczonym hackiem, który od tamtej pory znalazł zastosowanie w kilku niejasnych, szanowanych zastosowaniach?
Edytuj dla wyjaśnienia: mówię o następującym scenariuszu, a nie o sytuacji, w której dzieci A są narażone na B lub zarówno B, jak i jego dzieci. Mogę sobie również wyobrazić opcjonalne przyznanie dostępu do nadpisań funkcji znajomych itp.
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. */ };
Zaakceptowana odpowiedź: jak stwierdza Loki , efekt można symulować mniej więcej przez tworzenie chronionych funkcji proxy w zaprzyjaźnionych klasach bazowych, więc nie ma ścisłej potrzeby przyznawania przyjaźni z dziedziczeniem klas lub metod wirtualnych. Nie podoba mi się potrzeba używania standardowych serwerów proxy (którymi faktycznie staje się zaprzyjaźniona baza), ale przypuszczam, że uznano to za lepsze od mechanizmu językowego, który byłby częściej nadużywany przez większość czasu. Myślę, że nadszedł czas, abym kupił i przeczytał książkę Stroupstrup The Design and Evolution of C ++ , którą poleca wystarczająco dużo osób, aby uzyskać lepszy wgląd w tego typu pytania ...
Myślę, że odpowiedź na twoje pierwsze pytanie brzmi: "Czy przyjaciele twojego ojca mają dostęp do twoich szeregowych?"
źródło
Zaprzyjaźniona klasa może ujawnić swojego znajomego za pomocą funkcji akcesorów, a następnie udzielić dostępu za ich pośrednictwem.
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(); } };
Pozwala to na lepszą kontrolę niż opcjonalna przechodniość. Na przykład,
get_cash
może byćprotected
lub może dochodzić do protokołu dostępu wykonawczego ograniczony.źródło
;)
C ++ Standard, sekcja 11.4 / 8
Gdyby przyjaźń została odziedziczona, klasa, która nie miała być przyjacielem, nagle uzyskałaby dostęp do wewnętrznych elementów Twojej klasy, co narusza hermetyzację.
źródło
Ponieważ to jest po prostu niepotrzebne.
Użycie
friend
słowa kluczowego samo w sobie jest podejrzane. Pod względem sprzężenia to najgorszy związek (znacznie wyprzedzający dziedziczenie i skład).Każda zmiana w wnętrzu klasy może mieć wpływ na przyjaciół z tej klasy… czy naprawdę chcesz mieć nieznaną liczbę przyjaciół? Nie byłbyś nawet w stanie ich wymienić, gdyby ci, którzy po nich dziedziczą, mogliby być również przyjaciółmi, a za każdym razem ryzykowałbyś złamanie kodu swoich klientów, z pewnością nie jest to pożądane.
Przyznaję, że w przypadku prac domowych / projektów domowych zależność jest często odległa. W przypadku małych projektów nie ma to znaczenia. Ale gdy tylko kilka osób pracuje nad tym samym projektem, a ten rozrósł się do dziesiątek tysięcy linii, musisz ograniczyć wpływ zmian.
Prowadzi to do bardzo prostej zasady:
Zmiana elementów wewnętrznych klasy powinna wpływać tylko na samą klasę
Oczywiście prawdopodobnie wpłyniesz na jego znajomych, ale są tu dwa przypadki:
std::ostream& operator<<(...)
tutaj, która nie jest członkiem wyłącznie przez przypadek reguł językaPoleciłbym użycie prostej metody:
class Example; class ExampleKey { friend class Example; ExampleKey(); }; class Restricted { public: void forExampleOnly(int,int,ExampleKey const&); };
Ten prosty
Key
wzorzec pozwala na zadeklarowanie przyjaciela (w pewnym sensie) bez faktycznego przyznawania mu dostępu do twoich wewnętrznych, tym samym izolując go od zmian. Ponadto pozwala temu przyjacielowi pożyczyć swój klucz powiernikom (takim jak dzieci), jeśli jest to wymagane.źródło
Przypuszczenie: jeśli klasa deklaruje inną klasę / funkcję jako znajomego, to dlatego, że ta druga jednostka potrzebuje uprzywilejowanego dostępu do pierwszej. Jaki jest pożytek z przyznania drugiej jednostce uprzywilejowanego dostępu do dowolnej liczby klas wywodzących się z pierwszej?
źródło
B
będzie miał dostęp do wszystkich klas odziedziczonych poA
...Klasa pochodna może dziedziczyć tylko coś, co jest „członkiem” bazy. Deklaracja znajomego nie jest członkiem klasy zaprzyjaźniającej się.
i dalej
Skoro „przyjaciel” nie jest po pierwsze członkiem klasy bazowej, w jaki sposób może być dziedziczony przez klasę pochodną?
źródło
Funkcja znajomego w klasie przypisuje do funkcji właściwość extern. tzn. extern oznacza, że funkcja została zadeklarowana i zdefiniowana gdzieś poza klasą.
Stąd oznacza to, że funkcja zaprzyjaźniona nie jest członkiem klasy. Dziedziczenie pozwala więc dziedziczyć tylko właściwości klasy, a nie rzeczy zewnętrzne. A także jeśli dziedziczenie jest dozwolone dla funkcji zaprzyjaźnionych, to dziedziczenie klasy innej firmy.
źródło
Przyjaciel jest dobry w dziedziczeniu jak interfejs w stylu dla kontenera Ale dla mnie, jak pierwszy powiedział, C ++ nie ma dziedziczenia propagowalnego
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 } };
Dla mnie problemem C ++ jest brak bardzo dobrej szczegółowości, aby kontrolować dostęp z dowolnego miejsca do czegokolwiek:
przyjaciel Rzecz może stać się przyjacielem Rzecz. *, aby zapewnić dostęp wszystkim dzieciom Rzeczy
Co więcej, przyjaciel [nazwany obszar] Rzecz. *, Aby przyznać dostęp do precyzyjnego, znajdują się w klasie kontenera za pośrednictwem specjalnego obszaru nazwanego dla znajomego.
Ok, zatrzymaj sen. Ale teraz znasz ciekawe zastosowanie znajomego.
W innej kolejności można również uznać za interesujące, że wszystkie klasy są przyjazne dla siebie. Innymi słowy, instancja klasy może
bez ograniczeń wywołać wszystkich członków innej instancji o tej samej nazwie:
class Object { private: void test() {} protected: void callAnotherTest(Object* anotherObject) { //private, but yes you can call test() from //another object instance anotherObject)->test(); } };
źródło
Prosta logika: „Mam przyjaciółkę Jane. To, że wczoraj zostaliśmy przyjaciółmi, nie sprawia, że wszyscy jej przyjaciele są moimi.
Nadal muszę zaakceptować te indywidualne przyjaźnie, a poziom zaufania byłby odpowiedni.
źródło