C ++ do znajomego lub nie do znajomego

19

W tym semestrze na studiach mam programowanie obiektowe z kursem c ++ i uczyliśmy się o funkcjach przyjaciela.

Instynktownie ich nie lubię za to, że potrafią ominąć zabezpieczenia zapewniane przez enkapsulację i ukrywanie danych. Przeczytałem kilka artykułów w Internecie, a niektórzy uważali, że to dobry pomysł, jeśli ma uzasadnione zastosowanie.

Co powiedziałby ekspert OOP o funkcjach znajomych w C ++? Czy powinienem to przejrzeć, czy powinienem dowiedzieć się więcej na ten temat?

nikhil
źródło
@all: Niesamowite odpowiedzi i komentarze. To świetny sposób na naukę. Nie ma mowy, abym nauczył się o takich szczegółach w podręczniku.
nikhil

Odpowiedzi:

13

Nie zawsze wygodne jest tworzenie wszystkich funkcji związanych z członkami klasy C ++ tej klasy. Wyobraźmy sobie na przykład implementację algebry wektorowej z pomnożeniem skalarnym. Chcemy napisać:

 double a;
 Vector v, w;
 w = v * a;

Możemy to zrobić za pomocą funkcji członka:

public class Vector {
 ...
 Vector operator*(double a);
}

Ale chcielibyśmy również napisać:

w = a * v

Wymaga to bezpłatnej funkcji:

 Vector operator*(double a, Vector v)

Słowo friendkluczowe zostało dodane do C ++ w celu obsługi tego użycia. Funkcja swobodna jest częścią implementacji klasy Vector i powinna zostać zadeklarowana w tym samym nagłówku i zaimplementowana w tym samym pliku źródłowym.

Podobnie możemy frienduprościć implementację ściśle powiązanych klas, takich jak kolekcja i iterator. Ponownie zadeklaruję obie klasy w tym samym nagłówku i zaimplementuję je w tym samym pliku źródłowym.

Kevin Cline
źródło
3
„Wymaga to bezpłatnej funkcji”. Nie, to nie: inline Vector operator*(double a, Vector v) { return v*a; }. W rzeczywistości rozwiązanie kanoniczne.
MSalters
1
@MSalters: Dobra uwaga. Wybrałem zły przykład. Myślę, że twoja funkcja wbudowana jest z definicji funkcją bezpłatną, ale deklaracja znajomego nie jest wymagana.
kevin cline,
4
@MSalters: Jest to poprawne tylko wtedy, gdy * jest przemiennym względem a oraz v (x). Jeśli elementy wektorowe są ogólne (niekoniecznie skalary), musisz zachować kolejność operandów
Emilio Garavaglia
To dość teoretyczne. Być może jedynym powszechnym przypadkiem nieprzemiennym byłby inline Vector operator*(double a, Vector v) { return -v*a; }i który wciąż nie wymaga przyjaźni.
MSalters
16

Funkcje zaprzyjaźnienia nie różnią się od funkcji składowych pod względem enkapsulacji. Mogą one jednak oferować inne zalety - na przykład bardziej ogólne, zwłaszcza jeśli chodzi o szablony. Ponadto niektórych operatorów można określić tylko jako funkcje bezpłatne, więc jeśli chcesz, aby mieli dostęp do członków, musisz friend.

Lepiej jest friendużyć jednej funkcji niż być zmuszonym do zrobienia czegoś, czego nie chcesz być publicznym. Oznacza to, że cały świat może z niego korzystać zamiast jednej funkcji.

DeadMG
źródło
Funkcje +1 dla znajomych nie różnią się od funkcji członkowskich pod względem enkapsulacji. Dotyczy to tylko funkcji członka publicznego.
TheFogger
1
@TheFogger: Prawdopodobnie możesz również friendmieć funkcję „prywatną”, taką jak zadeklarowana tylko w jednej JT.
DeadMG,
5

Jeśli pasjonujesz się tym, co robisz, nauczyłbyś się wszystkiego o C ++. Dowiedz się, do czego służą, jak ich używać, a następnie - i tylko wtedy - zdecyduj, aby ich nie używać. Przynajmniej będziesz przygotowany podczas czytania kodu innej osoby korzystającego z tego aspektu C ++.

JK
źródło
5

Co powiedziałby ekspert OOP ... ” To zależy głównie od tego, jak on jest ekspertem w C ++, że - zgodnie z jego własną specyfikacją - nie jest (i nie chce być) językiem purystów.

Zeloci OOP nie używają C ++ (wolą Smalltalk i jak Java).

Zeloty programowania funkcjonalnego nie używają C ++ (wolą LISP i jego następców)

Większość ekspertów OOP nie lubi funkcji znajomych po prostu dlatego, że chcą, aby część OOP w C ++ zachowywała się jak Smalltalk. Ale C ++ nie jest Smalltalk i nie mogą nawet zrozumieć, że przyjaciel nie przerywa enkapsulacji , z bardzo prostego powodu, że funkcja nie może być przyjacielem twojej klasy bez twojej klasy .

A z punktu widzenia „funkcjonalności” pomiędzy a.fn(b)i fn(a,b)nie ma różnicy (gdzie fnjest przyjaciel): zaangażowane strony są takie same. Wystarczy jeden składnia może być bardziej odpowiednie niż inny: jeśli fn jest przemienne czasowo ai b, fn(a,b)prawdopodobnie bardziej nadaje następnie a.fn(b)(. A gdzie wygląd posiadające „szczególną rolę”, że w rzeczywistości nie ma)

Emilio Garavaglia
źródło
1
„Zealoci OOP”, którzy lubią Javę, nie rozumieli OOP. Getters? Setery? Nie ma prostej składni dla zamknięć? Parafrazując Alana Kay, nie tak wyobrażał sobie OOP.
Konrad Rudolph,
@Konrad: fanatycy są niezwykle nieograniczonym zestawem. Zawsze jest gorliwy fanatyk niż dany fanatyk.
Emilio Garavaglia,
Muszę powiedzieć, że zagłosowałem, ponieważ naprawdę spodobał mi się ten ostatni akapit. Ma sens.
julealgon
5

Czy „przyjaciel” narusza enkapsulację?

Nie. „Przyjaciel” jest jawnym mechanizmem przyznawania 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.

fredoverflow
źródło
2

C ++ FAQ jest zwięzły:

Użyj członka, kiedy możesz, i przyjaciela, kiedy musisz.

FAQ przedstawia jeden z bardziej użytecznych sposobów myślenia o przyjaźni:

Wiele osób uważa, że ​​przyjaciel funkcjonuje jak coś poza klasą. Zamiast tego spróbuj wymyślić funkcję przyjaciela jako część publicznego interfejsu klasy. Funkcja „przyjaciel” w deklaracji klasy nie narusza enkapsulacji tak samo, jak funkcja członka publicznego narusza enkapsulację: obie mają dokładnie takie same uprawnienia w zakresie dostępu do niepublicznych części klasy.

Być może najczęstszym zastosowaniem funkcji znajomych jest przeciążanie << dla I / O.

Gryźć
źródło
0

Funkcje zaprzyjaźnienia najlepiej nadają się do definicji operatora zdefiniowanych przez użytkownika. Przydają się w innych sytuacjach, ale jeśli często określasz klasy znajomych, możesz być na objeździe projektowym (po prostu dobry test do użycia podczas pisania kodu).

Zachowaj ostrożność w stosunku do stwierdzenia „bezpieczeństwo” w pierwotnym pytaniu. Dostępne są modyfikatory dostępu, które zapobiegają pisaniu złego kodu po wypadku, podobnie jak kompilator. Modyfikatory dostępu ograniczają interfejs i służą do komunikowania, jakie funkcje są ważne dla korzystania z klasy (publicznej i chronionej), a które zostały utworzone w ramach upiększania klasy dla opiekunów (prywatnej). Modyfikatory nie stanowią bezpieczeństwa, ponieważ istnieje wiele sposobów na uzyskanie prywatnych danych. Na przykład zdobądź wskaźnik do klasy i jej wielkości i idź łowić ryby.

zaraz
źródło
-2

Funkcje znajomych w C ++ są ściśle powiązane z następującymi funkcjami:

  1. darmowe funkcje
  2. funkcje statyczne
  3. funkcje przyjaciela

Oznacza to, że nie mają tego wskaźnika, a zatem znajdują się poza klasą / obiektem. Z drugiej strony często przyjmują parametry, które sprawiają, że ponownie należą do klasy. Oto przykład, który wyjaśnia link:

class B;
class A {
public:
    friend void f(A &a, B &b);
private:
    int m_a;
};
class B {
public:
   friend void f(A &a, B &b);
private:
   int m_b;
};
void f(A &a, B &b) { /* uses both A's and B's private data */ }

Jedyną różnicą między funkcjami statycznymi a funkcjami znajomego jest to, że funkcja znajomego może korzystać z kilku klas.

Korzystanie z mechanizmu zaprzyjaźnionego w c ++ wymaga programistów, którzy mają około 10-15 lat doświadczenia ze sposobem programowania w c ++, dlatego na początku powinieneś go unikać. To zaawansowana funkcja.

tp1
źródło
7
I jak wyprowadziłeś 10-15 lat?
DeadMG,
10-15 lat pochodzi od momentu, w którym stało się to rzeczywiście konieczne.
tp1
3
Więc dowolnie wymyśliłeś liczbę.
DeadMG
3
-1: „Powinieneś tego unikać”. Każda funkcja C ++ została stworzona w celu rozwiązania problemu. Gdy pojawi się ten problem, użyj odpowiedniej funkcji.
kevin cline
Dzięki za -1. Był powód tego komentarza. Sądzę, że to po prostu trudna koncepcja, że ​​nie wszystkie funkcje są odpowiednie dla początkujących.
tp1