Kiedy należy używać „przyjaciela” w C ++?

354

Czytałem przez C ++ FAQ i byłem ciekawy frienddeklaracji. Osobiście nigdy go nie używałem, ale interesuje mnie odkrywanie języka.

Jaki jest dobry przykład używania friend?


Trochę dłużej czytając FAQ Podoba mi się pomysł << >>przeciążania operatora i dodawania go jako przyjaciela tych klas. Nie jestem jednak pewien, w jaki sposób nie przerywa to enkapsulacji. Kiedy te wyjątki mogą pozostać w ramach surowej zasady, którą jest OOP?

Peter Mortensen
źródło
5
Chociaż zgadzam się z odpowiedzią, że klasa przyjaciół niekoniecznie jest Złą Rzeczą, zwykle traktuję ją jako niewielki kod. Często, choć nie zawsze, wskazuje, że hierarchia klas wymaga ponownego rozważenia.
Mawg mówi o przywróceniu Moniki
1
Użyłbyś klasy przyjaciela, w której już istnieje ścisłe połączenie. Po to jest zrobione. Na przykład tabela bazy danych i jej indeksy są ściśle powiązane. Gdy tabela się zmienia, wszystkie jej indeksy muszą zostać zaktualizowane. Tak więc klasa DBIndex zadeklaruje DBTable jako przyjaciela, aby DBTable miał bezpośredni dostęp do wewnętrznych elementów indeksu. Ale nie byłoby publicznego interfejsu do DBIndex; nie ma sensu nawet czytać indeksu.
shawnhcorey
„Puryści” OOP z niewielkim praktycznym doświadczeniem twierdzą, że przyjaciel narusza zasady OOP, ponieważ klasa powinna być jedynym opiekunem swojego prywatnego państwa. Jest to w porządku, dopóki nie napotkasz wspólnej sytuacji, w której dwie klasy muszą utrzymywać wspólny stan prywatny.
kaalus

Odpowiedzi:

335

Po pierwsze (IMO) nie słuchaj ludzi, którzy twierdzą, że friendnie jest to przydatne. To jest użyteczne. W wielu sytuacjach będziesz mieć obiekty z danymi lub funkcjami, które nie mają być publicznie dostępne. Jest to szczególnie prawdziwe w przypadku dużych baz kodowych z wieloma autorami, którzy mogą jedynie powierzchownie znać różne obszary.

Istnieją alternatywy dla specyfikatora przyjaciela, ale często są one uciążliwe (klasy betonowe na poziomie cpp / maskowane typy typef) lub niezawodnie (komentarze lub konwencje nazw funkcji).

Na odpowiedź;

Specyfikator friendumożliwia wyznaczonej klasie dostęp do chronionych danych lub funkcji w obrębie klasy, czyniąc wypowiedź znajomego. Na przykład w poniższym kodzie każdy może poprosić dziecko o swoje imię, ale tylko matka i dziecko mogą zmienić imię.

Możesz pójść dalej tym prostym przykładem, rozważając bardziej złożoną klasę, taką jak Window. Całkiem prawdopodobne, że okno będzie zawierało wiele funkcji / elementów danych, które nie powinny być publicznie dostępne, ale SĄ wymagane przez powiązaną klasę, taką jak 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
źródło
114
Jako dodatkową uwagę, C ++ FAQ wspomina, że friend poprawia enkapsulację. friendzapewnia członkom selektywny dostęp , podobnie jak to protectedrobi. Każda drobiazgowa kontrola jest lepsza niż przyznanie publicznego dostępu. Inne języki również definiują mechanizmy dostępu selektywnego, rozważmy C # internal. Najbardziej negatywna krytyka friendzwiązana z używaniem jest związana z ściślejszym sprzężeniem, co ogólnie uważa się za coś złego. Jednak w niektórych przypadkach ściślejsze sprzężenie jest dokładnie tym, czego chcesz i frienddaje ci tę moc.
André Caron
5
Czy możesz powiedzieć coś więcej o (klasach betonowych na poziomie cpp) i (maskowanych typedefach), Andrew ?
OmarOthman
18
Ta odpowiedź wydaje się bardziej skoncentrowana na wyjaśnieniu friend, niż na dawaniu motywującego przykładu. Przykład Window / WindowManager jest lepszy niż pokazany przykład, ale zbyt niejasny. Ta odpowiedź nie dotyczy również części enkapsulacji pytania.
bames53
4
Tak skutecznie istnieje „przyjaciel”, ponieważ C ++ nie ma pojęcia o pakiecie, w którym wszyscy członkowie mogą udostępniać szczegóły implementacji? Byłbym bardzo zainteresowany przykładem z prawdziwego świata.
weberc2
1
@OMGtechy Nie musiałbyś tego robić, gdyby C ++ miał pojęcie pakietów, więc jest to zgodne z moim poprzednim stwierdzeniem. Oto przykład Przejdź który używa pakietów zamiast przyjaciół aby uzyskać dostęp do prywatnych członków: play.golang.org/p/xnade4GBAL
weberc2
162

W pracy używamy znajomych do intensywnego testowania kodu . Oznacza to, że możemy zapewnić właściwe enkapsulację i ukrywanie informacji dla głównego kodu aplikacji. Ale możemy też mieć osobny kod testowy, który używa znajomych do sprawdzania stanu wewnętrznego i danych do testowania.

Wystarczy powiedzieć, że nie użyłbym słowa kluczowego „przyjaciel” jako niezbędnego elementu twojego projektu.

Daemin
źródło
Właśnie do tego używam. To lub po prostu ustaw zmienne składowe na chronione. Szkoda tylko, że nie działa w C ++ / CLI :-(
Jon Cage
12
Osobiście odradzałbym to. Zwykle testujesz interfejs, tzn. Czy zestaw danych wejściowych daje oczekiwany zestaw danych wyjściowych. Dlaczego musisz sprawdzać dane wewnętrzne?
Graeme,
55
@Graeme: Ponieważ dobry plan testów obejmuje testy zarówno białej skrzynki, jak i czarnej skrzynki.
Ben Voigt
1
Zwykle zgadzam się z @Graeme, jak doskonale wyjaśniono w tej odpowiedzi .
Alexis Leclerc,
2
@Graeme może nie być bezpośrednio danymi wewnętrznymi. Mogę być metodą, która wykonuje określoną operację lub zadanie na danych, jeżeli metoda ta jest prywatna dla klasy i nie powinna być publicznie dostępna, podczas gdy jakiś inny obiekt może potrzebować karmić lub inicjować chronioną metodę tej klasy własnymi danymi.
Francis Cugler,
93

Słowo friendkluczowe ma wiele dobrych zastosowań. Oto dwa zastosowania natychmiast widoczne dla mnie:

Definicja przyjaciela

Definicja znajomego pozwala zdefiniować funkcję w zakresie klasy, ale funkcja ta nie będzie zdefiniowana jako funkcja składowa, ale jako wolna funkcja otaczającej przestrzeni nazw i nie będzie widoczna normalnie, z wyjątkiem wyszukiwania zależnego od argumentów. Dzięki temu jest szczególnie przydatny w przypadku przeciążenia operatora:

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
}

Prywatna klasa bazowa CRTP

Czasami okazuje się, że polityka potrzebuje dostępu do klasy pochodnej:

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

W tej odpowiedzi znajdziesz nieprzygotowany przykład . Inny kod używający tego jest w tej odpowiedzi. Baza CRTP rzutuje ten wskaźnik, aby móc uzyskać dostęp do pól danych klasy pochodnej za pomocą wskaźników elementów danych.

Johannes Schaub - litb
źródło
Cześć, dostaję błąd składniowy (w xcode 4), gdy wypróbuję twój CRTP. Xcode uważa, że ​​próbuję odziedziczyć szablon klasy. Błąd występuje w P<C>w template<template<typename> class P> class C : P<C> {};stwierdzające, „Korzystanie z szablonu klasy C wymaga argumentów szablonu”. Masz takie same problemy lub może znasz rozwiązanie?
bennedich
@bennedich na pierwszy rzut oka wygląda na taki błąd, jaki można uzyskać przy niewystarczającej obsłudze funkcji C ++. Co jest dość powszechne wśród kompilatorów. Użycie FlexibleClasswewnątrz FlexibleClasspowinno domyślnie odnosić się do jego własnego typu.
Yakk - Adam Nevraumont
@bennedich: Zasady używania nazwy szablonu klasy z treści klasy zostały zmienione w C ++ 11. Spróbuj włączyć tryb C ++ 11 w kompilatorze.
Ben Voigt
W Visual Studio 2015 dodaj ten publiczny: f () {}; f (int_type t): wartość (t) {}; Aby uniknąć tego błędu kompilatora: błąd C2440: „<funkcja-funkcji>”: nie można przekonwertować z „utils :: f :: int_type” na „utils :: f” Uwaga: Żaden konstruktor nie może wziąć typu źródłowego ani konstruktora rozwiązanie przeciążenia było niejednoznaczne
Damian
41

@roo : Hermetyzacja nie jest tu zerwana, ponieważ sama klasa decyduje, kto może uzyskać dostęp do jej prywatnych członków. Hermetyzacja zostałaby zerwana tylko wtedy, gdyby mogła być spowodowana spoza klasy, np. operator <<Gdybyś ogłosił „Jestem przyjacielem klasy foo”.

friendzastępuje użycie public, a nie użycie private!

W rzeczywistości C ++ FAQ już na to odpowiada .

Konrad Rudolph
źródło
14
„! przyjaciel zastępuje korzystać z publiczności, a nie korzystać z prywatnego”, to drugi, że
Waleed Eissa
26
@Assaf: tak, ale FQA jest w większości bardzo niespójnym, wściekłym bełkotem bez żadnej realnej wartości. Ta część friendnie jest wyjątkiem. Jedyną prawdziwą obserwacją jest to, że C ++ zapewnia enkapsulację tylko w czasie kompilacji. I nie potrzebujesz więcej słów, żeby to powiedzieć. Reszta to pierdoły. Podsumowując: ta sekcja FQA nie jest warta wspomnienia.
Konrad Rudolph,
12
Większość tego FQA to całkowity blx :)
rama-jka toti
1
@Konrad: „Jedyną prawdziwą obserwacją jest to, że C ++ zapewnia enkapsulację tylko w czasie kompilacji.” Czy jakieś języki zapewniają to w czasie wykonywania? O ile mi wiadomo, zwracanie referencji do prywatnych członków (i funkcji, dla języków, które zezwalają na wskaźniki do funkcji lub funkcji jako obiektów pierwszej klasy) jest dozwolone w C #, Java, Python i wielu innych.
André Caron
@ André: JVM i CLR faktycznie mogą to zapewnić, o ile mi wiadomo. Nie wiem, czy zawsze tak się dzieje, ale rzekomo możesz chronić pakiety / zestawy przed taką ingerencją (chociaż sam tego nigdy nie robiłem).
Konrad Rudolph
27

Kanonicznym przykładem jest przeciążenie operatora <<. Innym powszechnym zastosowaniem jest umożliwienie klasie pomocnika lub administratora dostępu do wewnętrznych elementów.

Oto kilka wskazówek, które słyszałem o znajomych w C ++. Ten ostatni jest szczególnie niezapomniany.

  • Twoi przyjaciele nie są przyjaciółmi twojego dziecka.
  • Przyjaciele twojego dziecka nie są przyjaciółmi.
  • Tylko znajomi mogą dotykać twoich prywatnych części.
Mark Harrison
źródło
Kanonicznym przykładem jest przeciążenie operatora <<.friendChyba kanoniczny brak używania .
ciekawy,
16

edytuj: Trochę dłużej czytając faq Podoba mi się pomysł przeciążania operatora << >> i dodawania go jako przyjaciela tych klas, jednak nie jestem pewien, jak to nie przerywa enkapsulacji

Jak przerwałoby to enkapsulację?

Przerywasz enkapsulację, gdy zezwalasz na nieograniczony dostęp do elementu danych. Rozważ następujące klasy:

class c1 {
public:
  int x;
};

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

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

c1jest oczywiście nie obudowane. Każdy może w nim czytać i modyfikować x. Nie mamy możliwości egzekwowania jakiejkolwiek kontroli dostępu.

c2jest oczywiście zamknięty. Nie ma publicznego dostępu do x. Wszystko, co możesz zrobić, to wywołać foofunkcję, która wykonuje pewne znaczące operacje na klasie .

c3? Czy to jest mniej zamknięte? Czy pozwala na nieograniczony dostęp x? Czy umożliwia dostęp do nieznanych funkcji?

Nie. Pozwala dokładnie jednej funkcji na dostęp do prywatnych członków klasy. Tak jak c2zrobiłem. I podobnie c2, jedyną funkcją, która ma dostęp, nie jest „jakaś losowa, nieznana funkcja”, ale „funkcja wymieniona w definicji klasy”. Podobnie jak c2widzimy, po prostu patrząc na definicje klas, pełną listę osób, które mają dostęp.

Jak dokładnie jest to mniej zamknięte? Ta sama ilość kodu ma dostęp do prywatnych członków klasy. I każdy, kto ma dostęp, jest wymieniony w definicji klasy.

friendnie przerywa enkapsulacji. To sprawia, że ​​niektórzy programiści Java czują się niekomfortowo, ponieważ kiedy mówią „OOP”, w rzeczywistości mają na myśli „Java”. Kiedy mówią „Encapsulation”, nie oznacza to „członkowie prywatne muszą być chronione przed arbitralnymi dostępów”, ale „klasy Java, gdzie funkcjonuje tylko możliwość dostępu do prywatnych członków są członkowie klasy”, choć jest to kompletny nonsens dla kilka powodów .

Po pierwsze, jak już pokazano, jest to zbyt restrykcyjne. Nie ma powodu, dla którego metody znajomych nie powinny robić tego samego.

Po drugie, nie jest to wystarczająco restrykcyjne . Rozważ czwartą klasę:

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

To, zgodnie z wyżej wspomnianą mentalnością Javy, jest doskonale zamknięte. A jednak pozwala absolutnie każdemu czytać i modyfikować x . Jak to w ogóle ma sens? (wskazówka: nie ma)

Konkluzja: Hermetyzacja polega na możliwości kontrolowania, które funkcje mogą uzyskiwać dostęp do członków prywatnych. To nie o dokładnie gdzie znajdują się definicje tych funkcji.

jalf
źródło
10

Inna popularna wersja przykładu Andrew, przerażający kodeks

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

Zamiast martwić się, że obie linie są zawsze wykonywane razem i w spójnej kolejności, możesz ustawić metody na prywatne i mieć funkcję przyjaciela, aby wymusić spójność:

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

Innymi słowy, możesz zmniejszyć interfejsy publiczne i wymusić niezmienniki, które przecinają klasy i obiekty w funkcjach znajomych.

maccullt
źródło
6
Dlaczego ktoś miałby do tego potrzebować przyjaciela? Dlaczego nie pozwolić addChildfunkcji członka ustawić również element nadrzędny?
Nawaz
1
Lepszym przykładem byłoby nawiązanie setParentznajomości, ponieważ nie chcesz zezwalać klientom na zmianę rodzica, ponieważ będziesz nim zarządzać w kategorii addChild/ removeChildfunkcji.
Ylisar
8

Kontrolujesz prawa dostępu dla członków i funkcji, korzystając z prawa prywatnego / chronionego / publicznego? więc zakładając, że idea każdego z tych 3 poziomów jest jasna, powinno być jasne, że czegoś nam brakuje ...

Deklaracja chronionego członka / funkcji na przykład jest dość ogólna. Mówisz, że ta funkcja jest niedostępna dla wszystkich (z wyjątkiem oczywiście odziedziczonego dziecka). Ale co z wyjątkami? każdy system bezpieczeństwa pozwala ci mieć jakąś „białą listę”, prawda?

Tak więc przyjaciel pozwala elastycznie izolować stałe obiekty skalne, ale pozwala stworzyć „lukę” dla rzeczy, które uważasz za uzasadnione.

Myślę, że ludzie mówią, że nie jest to potrzebne, ponieważ zawsze istnieje projekt, który da sobie z tym radę. Myślę, że jest to podobne do dyskusji na temat zmiennych globalnych: nigdy nie powinieneś ich używać, zawsze istnieje sposób na obejście się bez nich ... ale w rzeczywistości widzisz przypadki, w których jest to (prawie) najbardziej elegancki sposób. .. Myślę, że to samo dotyczy przyjaciół.

Naprawdę nie przynosi to żadnego pożytku, oprócz umożliwienia dostępu do zmiennej składowej bez użycia funkcji ustawiania

cóż, to nie jest dokładnie tak na to patrzeć. Chodzi o to, aby kontrolować, kto ma dostęp do tego, co ma, ale nie ma funkcji ustawień, ma z tym niewiele wspólnego.

csmba
źródło
2
Jak działa friendluka? Umożliwia metodom wymienionym w klasie dostęp do jej prywatnych członków. Nadal nie pozwala na dostęp do nich dowolnego kodu. Jako taki nie różni się niczym od funkcji członka publicznego.
czerwiec
przyjaciel jest tak blisko, jak to tylko możliwe, aby uzyskać dostęp do pakietu na poziomie C # / Java w C ++. @jalf - co z klasami przyjaciół (takimi jak klasa fabryczna)?
Ogre Psalm33
1
@Ogre: Co z nimi? Nadal dajesz tej klasie i nikomu innemu dostęp do jej wewnętrznych elementów. Nie tylko pozostawiasz otwartą bramę dla dowolnego nieznanego kodu, który może wkręcić się w twoją klasę.
lipiec
8

Znalazłem przydatne miejsce do korzystania z dostępu do znajomych: Najmniejsze prywatne funkcje.

VladimirS
źródło
Ale czy można do tego również wykorzystać funkcję publiczną? Jaka jest zaleta korzystania z dostępu do znajomych?
Zheng Qu
@Maverobot Czy mógłbyś rozwinąć swoje pytanie?
VladimirS
5

Przyjaciel przydaje się, gdy budujesz kontener i chcesz zaimplementować iterator dla tej klasy.

rptony
źródło
4

W firmie, w której wcześniej pracowałem, pojawił się interesujący problem, w którym użyliśmy znajomego do przyzwoitego afektu. Pracowałem w naszym dziale ramowym, stworzyliśmy podstawowy system poziomu silnika nad naszym niestandardowym systemem operacyjnym. Wewnętrznie mieliśmy strukturę klas:

         Game
        /    \
 TwoPlayer  SinglePlayer

Wszystkie te klasy były częścią frameworka i były prowadzone przez nasz zespół. Gry produkowane przez firmę zostały zbudowane na bazie tego środowiska pochodzącego od jednego z dzieci Games. Problem polegał na tym, że gra miała interfejsy do różnych rzeczy, do których SinglePlayer i TwoPlayer potrzebowali dostępu, ale że nie chcieliśmy ujawniać się poza klasami frameworka. Rozwiązaniem było uczynienie tych interfejsów prywatnymi i umożliwienie dostępu do nich TwoPlayer i SinglePlayer poprzez przyjaźń.

Prawdę mówiąc, cały ten problem mógł zostać rozwiązany przez lepszą implementację naszego systemu, ale byliśmy zamknięci w tym, co mieliśmy.

Promień
źródło
4

Krótka odpowiedź brzmiałaby: użyj znajomego, gdy rzeczywiście się poprawi enkapsulację. Poprawa czytelności i użyteczności (operatory << i >> są kanonicznym przykładem) jest również dobrym powodem.

Jeśli chodzi o przykłady poprawy enkapsulacji, klasy specjalnie zaprojektowane do pracy z elementami wewnętrznymi innych klas (przychodzą na myśl klasy testowe) są dobrymi kandydatami.

Gorpik
źródło
operatory << i >> są przykładem kanonicznym ”. Nie. Raczej kanoniczne przykłady liczników .
ciekawy,
@curiousguy: operatorzy <<i >>zwykle są przyjaciółmi, a nie członkami, ponieważ uczynienie ich członkami sprawiłoby, że korzystanie z nich byłoby niewygodne. Oczywiście mówię o przypadku, gdy operatorzy ci muszą uzyskać dostęp do prywatnych danych; w przeciwnym razie przyjaźń jest bezużyteczna.
Gorpik
ponieważ uczynienie ich członkami sprawiłoby, że korzystanie z nich byłoby niewygodne. ” Oczywiście tworzenie operator<<i operator>>członkowie klasy wartości zamiast osób niebędących członkami lub członkami i|ostreamnie zapewniłyby pożądanej składni, a ja tego nie sugeruję. „ Mówię o przypadku, w którym operatorzy ci muszą uzyskać dostęp do prywatnych danych ”. Nie do końca rozumiem, dlaczego operatorzy wejścia / wyjścia musieliby uzyskać dostęp do prywatnych członków.
ciekawy,
4

Twórca C ++ twierdzi, że nie łamie żadnej zasady enkapsulacji, a ja go zacytuję:

Czy „przyjaciel” narusza enkapsulację? Nie. „Przyjaciel” to jawny mechanizm udzielania dostępu, podobnie jak członkostwo. Nie możesz (w standardowym programie zgodnym) przyznać sobie dostępu do klasy bez modyfikacji jej źródła.

Jest więcej niż jasne ...

garzanti
źródło
@curiousguy: To prawda, nawet w przypadku szablonów.
Nawaz
@Nawaz Szablon przyjaźni może zostać przyznany, ale każdy może dokonać nowej częściowej lub wyraźnej specjalizacji bez modyfikowania klasy zapewniającej przyjaźń. Ale zachowaj ostrożność przy naruszaniu ODR. I tak nie rób tego.
ciekawy,
3

Inne zastosowanie: przyjaciel (+ wirtualne dziedziczenie) może być użyty, aby uniknąć wywodzenia się z klasy (aka: „uczyń klasę możliwą do podzielenia”) => 1 , 2

Od 2 :

 class Fred;

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

 class Fred : private virtual FredBase {
 public:
   ...
 }; 
Gian Paolo Ghilardi
źródło
3

Aby zrobić TDD wiele razy użyłem słowa kluczowego „przyjaciel” w C ++.

Czy przyjaciel może wiedzieć o mnie wszystko?


Zaktualizowano: Znalazłem tę cenną odpowiedź na temat słowa kluczowego „przyjaciel” ze strony Bjarne Stroustrup .

„Przyjaciel” to jawny mechanizm udzielania dostępu, podobnie jak członkostwo.

popopom
źródło
3

Musisz bardzo uważać, kiedy i gdzie używasz friendsłowa kluczowego, i tak jak ty, używałem go bardzo rzadko. Poniżej znajduje się kilka uwag dotyczących używania friendi alternatyw.

Powiedzmy, że chcesz porównać dwa obiekty, aby sprawdzić, czy są one równe. Możesz:

  • Użyj metod dostępowych, aby dokonać porównania (sprawdź każdy ivar i ustal równość).
  • Lub możesz uzyskać bezpośredni dostęp do wszystkich członków, upubliczniając ich.

Problem z pierwszą opcją polega na tym, że może to być DUŻO akcesoriów, które jest (nieco) wolniejsze niż bezpośredni dostęp do zmiennych, trudniejsze do odczytania i kłopotliwe. Problem z drugim podejściem polega na tym, że całkowicie przełamujesz enkapsulację.

Byłoby fajnie, gdybyśmy mogli zdefiniować funkcję zewnętrzną, która nadal mogłaby uzyskać dostęp do prywatnych członków klasy. Możemy to zrobić za pomocą friendsłowa kluczowego:

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

Metoda equal(Beer, Beer)ma teraz bezpośredni dostęp do ai b„s członków prywatnych (które mogą być char *brand, float percentAlcoholitp Jest to raczej wymyślony przykład, należy wcześniej zastosować frienddo przeciążony == operator, ale my się do tego.

Kilka rzeczy do zapamiętania:

  • A friendNIE jest funkcją składową klasy
  • Jest to zwykła funkcja ze specjalnym dostępem do prywatnych członków klasy
  • Nie wymieniaj wszystkich akcesoriów i mutatorów na znajomych (równie dobrze możesz zrobić wszystko public!)
  • Przyjaźń nie jest wzajemna
  • Przyjaźń nie jest przechodnia
  • Przyjaźń nie jest dziedziczona
  • Lub, jak wyjaśnia C ++ FAQ : „Tylko dlatego, że udzielam ci dostępu do przyjaźni do mnie, nie przyznaje automatycznie twoich dzieci dostępu do mnie, nie przyznaje automatycznie twoich znajomych do mnie i nie przyznaje mi automatycznie dostępu do ciebie . ”

Naprawdę używam tylko friendswtedy, gdy znacznie trudniej jest to zrobić w drugą stronę. Jako inny przykład, funkcje wielu matematyki wektorowe są często tworzone jako friendsze względu na interoperacyjność Mat2x2, Mat3x3, Mat4x4, Vec2, Vec3, Vec4, itd. A to jest po prostu o wiele łatwiej być przyjaciółmi, a nie trzeba używać akcesorów wszędzie. Jak wskazano, friendczęsto jest przydatny, gdy jest stosowany do <<(bardzo przydatny do debugowania) >>i może ==operatora, ale może być również używany do czegoś takiego:

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

Jak mówię, wcale nie używam friendzbyt często, ale od czasu do czasu jest to tylko to, czego potrzebujesz. Mam nadzieję że to pomoże!

Efemeryda
źródło
2

W odniesieniu do operatora << i operatora >> nie ma dobrego powodu, aby zaprzyjaźnić tych operatorów. To prawda, że ​​nie powinny to być funkcje członkowskie, ale nie muszą też być przyjaciółmi.

Najlepiej jest utworzyć funkcje drukowania publicznego (ostream &) i czytania (istream &). Następnie napisz operator << i operator >> pod względem tych funkcji. Daje to dodatkową korzyść polegającą na umożliwieniu wirtualizacji tych funkcji, co zapewnia wirtualną serializację.


źródło
W odniesieniu do operatora << i operatora >> nie ma dobrego powodu, aby zaprzyjaźnić tych operatorów. ” Absolutnie poprawne. „ Daje to dodatkową korzyść, umożliwiając wirtualizację tych funkcji, ”. Jeśli dana klasa jest przeznaczona do wyprowadzenia, tak. W przeciwnym razie po co zawracać sobie głowę?
ciekawy,
Naprawdę nie rozumiem, dlaczego ta odpowiedź została dwukrotnie zanegowana - i nawet bez wyjaśnienia! To jest niegrzeczne.
ciekawy,
virtual dodałby hit, który mógłby być dość duży w serializacji
paulm
2

Używam słowa kluczowego „przyjaciel” tylko do najbardziej chronionych funkcji. Niektórzy powiedzą, że nie powinieneś testować chronionych funkcji. Uważam jednak to bardzo przydatne narzędzie podczas dodawania nowej funkcjonalności.

Jednak nie używam tego słowa kluczowego bezpośrednio w deklaracjach klas, zamiast tego używam sprytnego hackowania szablonu, aby osiągnąć ten cel:

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

Dzięki temu mogę wykonać następujące czynności:

friendMe(this, someClassInstance).someProtectedFunction();

Działa na GCC i MSVC przynajmniej.

larsmoa
źródło
2

W C ++ słowo kluczowe „przyjaciel” jest przydatne w przeciążaniu operatora i tworzeniu mostu.

1.) Słowo kluczowe przyjaciela w przeciążeniu operatora:
Przykładem przeciążenia operatora jest: Załóżmy, że mamy klasę „Punkt”, która ma dwie zmienne zmienne
„x” (dla współrzędnej x) i „y” (dla współrzędnej y). Teraz musimy przeciążać "<<"(operator ekstrakcji) tak, że jeśli "cout << pointobj"wywołamy, to wypisze współrzędną xiy (gdzie pointobj jest obiektem klasy Point). Aby to zrobić, mamy dwie opcje:

   1. Przeciąż funkcję „operator << ()” w klasie „ostream”.
   2. Przeciąż funkcję „operator << ()” w klasie „Punkt”.
Teraz pierwsza opcja nie jest dobra, ponieważ jeśli musimy ponownie przeciążyć tego operatora dla innej klasy, musimy ponownie wprowadzić zmiany w klasie „ostream”.
Właśnie dlatego druga opcja jest najlepsza. Teraz kompilator może wywoływać "operator <<()"funkcję:

   1. Za pomocą obiektu ostream cout.As: cout.operator << (Pointobj) (z klasy ostream). 
2. Zadzwoń bez obiektu. Jak: operator << (cout, Pointobj) (z klasy Point).

Ponieważ wdrożyliśmy przeciążanie w klasie Point. Aby wywołać tę funkcję bez obiektu, musimy dodać "friend"słowo kluczowe, ponieważ możemy wywołać funkcję znajomego bez obiektu. Teraz deklaracja funkcji będzie jak:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"

2.) Słowo kluczowe przyjaciela przy tworzeniu mostu:
Załóżmy, że musimy stworzyć funkcję, w której musimy uzyskać dostęp do prywatnego członka dwóch lub więcej klas (ogólnie określanych jako „most”). Jak to zrobić:
Aby uzyskać dostęp do prywatnego członka klasy, powinien on być członkiem tej klasy. Aby uzyskać dostęp do prywatnego członka innej klasy, każda klasa powinna zadeklarować tę funkcję jako funkcję przyjaciela. Na przykład: Załóżmy, że istnieją dwie klasy A i B. Funkcja "funcBridge()"chce uzyskać dostęp do prywatnego członka obu klas. Następnie obie klasy powinny zadeklarować "funcBridge()"jako:
friend return_type funcBridge(A &a_obj, B & b_obj);

Myślę, że pomogłoby to zrozumieć słowo kluczowe znajomego.

jadnap
źródło
2

Jak wynika z odniesienia do deklaracji znajomego :

Deklaracja znajomego pojawia się w treści klasy i zapewnia dostęp do funkcji lub innej klasy prywatnym i chronionym członkom klasy, w której pojawia się deklaracja znajomego.

Przypominamy, że w niektórych odpowiedziach występują błędy techniczne, które mówią, że friendmogą odwiedzać tylko chronionych członków.

lixunhuan
źródło
1

Przykład drzewa jest całkiem dobrym przykładem: posiadanie obiektu zaimplementowanego w kilku różnych klasach bez relacji dziedziczenia.

Być może możesz też potrzebować ochrony konstruktora i zmusić ludzi do korzystania z fabryki „przyjaciela”.

... Ok, szczerze mówiąc, bez tego możesz żyć.

fulmikoton
źródło
1

Aby zrobić TDD wiele razy użyłem słowa kluczowego „przyjaciel” w C ++.
Czy przyjaciel może wiedzieć o mnie wszystko?

Nie, to tylko przyjaźń jednokierunkowa: `(

Maleństwo
źródło
1

Jednym konkretnym przykładem, którego używam, friendjest tworzenie klas Singleton . friendSłów kluczowych pozwala mi utworzyć funkcję dostępowej, która jest bardziej zwięzły niż zawsze o „GetInstance ()” metodę na klasy.

/////////////////////////
// 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
źródło
To może być kwestia gustu, ale nie sądzę, że zaoszczędzenie kilku naciśnięć klawiszy uzasadnia użycie tutaj znajomego. GetMySingleton () powinien być metodą statyczną klasy.
Gorpik
Prywatny c-tor wyłączyłby funkcję nieprzyjazną, aby utworzyć instancję MySingleton, więc tutaj potrzebne jest słowo kluczowe „przyjaciel”.
JBRWilkinson
@Gorpik „ To może być kwestia gustu, ale nie sądzę, aby zaoszczędzenie kilku naciśnięć klawiszy uzasadnia użycie tutaj znajomego ”. W każdym razie, friendnie nie potrzebują szczególnej „Uzasadnienie”, podczas dodawania funkcji członek nie.
ciekawy,
Singletons są uważane za złą praktyką i tak (Google „Singleton szkodliwe” i dostaniesz mnóstwo wyników jak to nie sądzę, używając funkcji wdrożyć antywzorzec projektowy można uznać za dobre wykorzystanie tej funkcji..
weberc2
1

Funkcje i klasy znajomych zapewniają bezpośredni dostęp do prywatnych i chronionych członków klasy, aby uniknąć przerwania enkapsulacji w ogólnym przypadku. Najczęściej używa się ostreamu: chcielibyśmy móc pisać:

Point p;
cout << p;

Może to jednak wymagać dostępu do prywatnych danych Point, dlatego definiujemy przeciążonego operatora

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

Istnieją jednak oczywiste konsekwencje enkapsulacji. Po pierwsze, teraz klasa lub funkcja przyjaciela ma pełny dostęp do WSZYSTKICH członków klasy, nawet tych, którzy nie odnoszą się do jej potrzeb. Po drugie, implementacje klasy i przyjaciela są teraz powiązane do tego stopnia, że ​​wewnętrzna zmiana w klasie może złamać przyjaciela.

Jeśli postrzegasz przyjaciela jako rozszerzenie klasy, to nie jest to logicznie rzecz biorąc problem. Ale w takim razie dlaczego przede wszystkim trzeba było spearingować przyjaciela.

Aby osiągnąć to samo, co zamierzają osiągnąć „przyjaciele”, ale bez przerywania enkapsulacji, można to zrobić:

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

Hermetyzacja nie jest zerwana, klasa B nie ma dostępu do wewnętrznej implementacji w A, ale wynik jest taki sam, jakbyśmy zadeklarowali B jako przyjaciela A. Kompilator zoptymalizuje wywołania funkcji, więc spowoduje to takie same instrukcje jako bezpośredni dostęp.

Myślę, że użycie „przyjaciela” jest po prostu skrótem z uzasadnioną korzyścią, ale określonym kosztem.

Lubo Antonov
źródło
1

To może nie być rzeczywista sytuacja użycia, ale może pomóc zilustrować użycie znajomego między klasami.

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

Klasa członków

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

Udogodnienia

class Amenity{};

Jeśli spojrzysz na związek tych klas tutaj; ClubHouse posiada wiele różnych rodzajów członkostwa i dostęp do członkostwa. Wszyscy członkowie pochodzą z klasy super lub podstawowej, ponieważ wszyscy mają wspólny identyfikator, a typ wyliczony, który jest wspólny, a klasy zewnętrzne mogą uzyskiwać dostęp do swoich identyfikatorów i typów poprzez funkcje dostępu znajdujące się w klasie podstawowej.

Jednak dzięki takiej hierarchii członków i klas pochodnych oraz ich relacji z klasą ClubHouse jedyną klasą pochodną, ​​która ma „specjalne uprawnienia”, jest klasa VIPMember. Klasa podstawowa i pozostałe 2 klasy pochodne nie mogą uzyskać dostępu do metody joinVIPEvent () ClubHouse, ale klasa członka VIP ma takie przywileje, jakby miała pełny dostęp do tego zdarzenia.

Tak więc z VIPMember i ClubHouse jest to dwukierunkowa ulica dostępu, gdzie pozostałe Klasy Członków są ograniczone.

Francis Francis Cugler
źródło
0

Podczas implementacji algorytmów drzewa dla klasy, kod ramowy, który dał nam prof, miał klasę drzewa jako przyjaciela klasy węzłów.

Naprawdę nie przynosi to żadnego pożytku, oprócz umożliwienia dostępu do zmiennej składowej bez użycia funkcji ustawiania.

Ryan Fox
źródło
0

Możesz korzystać z przyjaźni, gdy różne klasy (nie dziedzicząc jednej od drugiej) korzystają z prywatnych lub chronionych członków drugiej klasy.

Typowe przypadki użycia funkcji znajomych to operacje przeprowadzane między dwiema różnymi klasami uzyskującymi dostęp do prywatnych lub chronionych członków obu.

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

Możesz zobaczyć ten przykład, w którym metoda nie będąca członkiem uzyskuje dostęp do prywatnych członków klasy. Ta metoda musi być zadeklarowana w tej klasie jako przyjaciel klasy.

// 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
źródło
0

Prawdopodobnie coś pominąłem z powyższych odpowiedzi, ale innym ważnym pojęciem w enkapsulacji jest ukrywanie implementacji. Ograniczenie dostępu do prywatnych członków danych (szczegóły implementacji klasy) pozwala znacznie łatwiej modyfikować kod później. Jeśli znajomy uzyskuje bezpośredni dostęp do danych prywatnych, wszelkie zmiany w polach danych implementacyjnych (dane prywatne), złam kod, który uzyskuje dostęp do tych danych. Korzystanie z metod dostępu w większości eliminuje to. Myślę, że to dość ważne.

peterdcasey
źródło
-1

Państwo mogłoby stosować się do zasad najsurowszych i najczystsza OOP i upewnić się, że żaden z członków danych dla dowolnej klasy nawet akcesorów tak, że wszystkie obiekty muszą być jedynymi, które mogą wiedzieć o swoich danych z jedynym sposobem działania na nich jest poprzez pośrednie wiadomości , tj. metody.

Ale nawet C # ma wewnętrzne słowo kluczowe widoczności, a Java ma domyślną dostępność poziomu pakietu dla niektórych rzeczy. C ++ jest bliżej ideału OOP, minimalizując kompromis w zakresie widoczności w klasie, określając dokładnie, która inna klasa i tylko inne klasy mogą się w to zapoznać.

Tak naprawdę nie używam C ++, ale gdyby C # miał znajomych , zrobiłbym to zamiast wewnętrznego modyfikatora asemblera , którego faktycznie używam. Tak naprawdę nie psuje to incapsulation, ponieważ jednostką wdrażania w .NET jest zestaw.

Ale jest jeszcze atrybut InternalsVisibleTo Attribute (otherAssembly), który działa jak mechanizm zaprzyjaźnienia między zespołami . Microsoft używa tego do zestawów projektantów wizualnych .

Mark Cidade
źródło
-1

Znajomi są również przydatni w przypadku oddzwaniania. Można zaimplementować wywołania zwrotne jako metody statyczne

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

gdzie callbackpołączenia localCallbackwewnętrzne orazclientData ma w nim swoją instancję. W mojej opinii,

lub...

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

To pozwala na to, aby przyjaciel był zdefiniowany wyłącznie w cpp jako funkcja w stylu c, a nie zagracałaby klasę.

Podobnie, wzór, który często widziałem, polega na ułożeniu wszystkiego naprawdę prywatnych członków klasy w innej klasie, która jest zadeklarowana w nagłówku, zdefiniowana w cpp i zaprzyjaźniona. Pozwala to programowi kodującemu na ukrywanie złożoności i wewnętrznego działania klasy przed użytkownikiem nagłówka.

W nagłówku:

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

W cpp

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

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

Łatwiej jest ukryć rzeczy, które nie muszą widzieć w ten sposób.

roztrzaskać
źródło
1
Czy interfejsy nie byłyby czystszym sposobem na osiągnięcie tego? Co powstrzyma kogoś przed przeglądaniem MyFooPrivate.h?
JBRWilkinson,
1
Cóż, jeśli używasz prywatnych i publicznych do zachowania tajemnic, łatwo zostaniesz pokonany. Przez „ukrywanie” mam na myśli, że użytkownik MyFoo tak naprawdę nie musi widzieć członków prywatnych. Poza tym przydatne jest utrzymanie kompatybilności ABI. Jeśli uczynisz _private wskaźnikiem, prywatna implementacja może zmienić tyle, ile chcesz, bez dotykania publicznego interfejsu, zachowując w ten sposób kompatybilność ABI.
shash
Odwołujesz się do idiomu PIMPL; celem, dla którego nie jest to dodatkowe enkapsulacja, jak się wydaje, ale przeniesienie szczegółów implementacji z nagłówka, aby zmiana szczegółów implementacji nie wymusiła ponownej kompilacji kodu klienta. Co więcej, nie trzeba używać znajomego do implementacji tego idiomu.
weberc2
No tak. Jego głównym celem jest przeniesienie szczegółów implementacji. Znajomy tam jest przydatny do obsługi prywatnych członków w klasie publicznej z prywatnych lub odwrotnie.
shash