Dlaczego mogę uzyskać dostęp do zmiennych prywatnych w konstruktorze kopiującym?

88

Dowiedziałem się, że nigdy nie mogę uzyskać dostępu do zmiennej prywatnej, tylko z funkcją get w klasie. Ale w takim razie dlaczego mogę uzyskać do niego dostęp w konstruktorze kopiującym?

Przykład:

Field::Field(const Field& f)
{
  pFirst = new T[f.capacity()];

  pLast = pFirst + (f.pLast - f.pFirst);
  pEnd  = pFirst + (f.pEnd - f.pFirst);
  std::copy(f.pFirst, f.pLast, pFirst);
}

Moja deklaracja:

private:
  T *pFirst,*pLast,*pEnd;
Król demonów
źródło
Ponieważ konstruktor kopiujący jest domyślnie członkiem klasy, podobnie jak niektóre inne.
DumbCoder
+ 53 / -0? Kto na to głosował? Jak inaczej byś je skopiował ?!? (Obalmy te, które nie są alternatywami: czy utworzyć publiczny pobieracz referencji dla każdego członka prywatnego? Wtedy wcale nie są prywatne. Utwórz publiczny const&lub pobierający według wartości dla każdego? Wtedy są one tylko „prywatne do zapisu”, & ponieważ wartości marnują zasoby i zawodzą dla członków, których nie można skopiować.) Jestem zdumiony takim sukcesem tak pustego pytania, pytającego o tworzenie kopii, całkowicie ignorując jego znaczenie, i żadna odpowiedź nie używa podstawowej logiki do obalenia tego. Wyjaśniają suche szczegóły techniczne, ale jest znacznie prostsza odpowiedź na pytanie, które migało
podkreślenie_d
8
@underscore_d, "Jak inaczej byś je skopiował?" to moim zdaniem bardzo dziwna odpowiedź. To jak odpowiedź „jak działa grawitacja?” z "jak inaczej by się to wszystko zawaliło!" Mylenie enkapsulacji na poziomie klasy z enkapsulacją na poziomie obiektu jest w rzeczywistości dość powszechne. To zabawne, jak myślisz, że to głupie pytanie i że odpowiedź powinna być oczywista. Należy pamiętać, że hermetyzacja w Smalltalk (prawdopodobnie archetypowym języku OO) faktycznie działa na poziomie obiektu.
aioobe
@aioobe Słuszna uwaga, dzięki. Mój komentarz jest trochę ekstremalny - być może ekspres do kawy był tego dnia zepsuty. Doceniam, że wskazałeś, dlaczego to pytanie byłoby popularne, szczególnie wśród osób wywodzących się z innych (i być może więcej) języków OO. Faktycznie, można się spierać, że mój komentarz był tym, co "mrugało", ponieważ pisałem z perspektywy kogoś, kto głównie programuje w C ++. Uwielbiam też tę analogię grawitacji!
underscore_d

Odpowiedzi:

33

IMHO, istniejące odpowiedzi słabo wyjaśniają „dlaczego” tego - koncentrując się zbytnio na powtarzaniu, jakie zachowanie jest prawidłowe. „modyfikatory dostępu działają na poziomie klasy, a nie obiektu”. - tak ale dlaczego?

Nadrzędną koncepcją jest tutaj to, że to programista (programiści) projektujący, piszący i utrzymujący klasę powinien (powinni) rozumieć enkapsulację obiektową pożądaną i uprawnioną do koordynowania jej implementacji. Tak więc, pisząc class X, kodujesz nie tylko to, jak pojedynczy X xobiekt może być używany przez kod z dostępem do niego, ale także:

  • klasy pochodne są w stanie współdziałać z nim (za pośrednictwem opcjonalnie czystych funkcji wirtualnych i / lub chronionego dostępu) i
  • różne Xobiekty współpracują w celu zapewnienia zamierzonych zachowań, jednocześnie przestrzegając warunków końcowych i niezmienników z projektu.

To nie tylko konstruktor kopiujący - bardzo wiele operacji może obejmować dwie lub więcej instancji Twojej klasy: jeśli porównujesz, dodajesz / mnożesz / dzielisz, kopiujesz konstruowanie, klonowanie, przypisywanie itp., To często jest tak, że albo po prostu musi mieć dostęp do prywatnych i / lub chronionych danych w innym obiekcie, albo chce, aby umożliwiał prostszą, szybszą lub ogólnie lepszą implementację funkcji.

W szczególności te operacje mogą chcieć skorzystać z uprzywilejowanego dostępu, aby wykonać następujące czynności:

  • (konstruktory kopiujące) używają prywatnego elementu członkowskiego obiektu "rhs" (po prawej stronie) na liście inicjalizatorów, tak że zmienna składowa jest sama konstruowana przez kopiowanie zamiast konstruowana domyślnie (jeśli nawet legalna), a następnie przypisywana (ponownie, jeśli legalne)
  • współdziel zasoby - uchwyty plików, segmenty pamięci współdzielonej, shared_ptrs do danych referencyjnych itp.
  • przejąć na własność rzeczy, np. auto_ptr<>„przenosi” własność na obiekt w budowie
  • kopiuj prywatne "pamięci podręczne", elementy kalibracji lub stanu potrzebne do skonstruowania nowego obiektu w optymalnie użytecznym stanie bez konieczności ponownego generowania ich od podstaw
  • kopiuj / uzyskuj dostęp do informacji diagnostycznych / śledzenia przechowywanych w kopiowanym obiekcie, które nie są w inny sposób dostępne za pośrednictwem publicznych interfejsów API, ale mogą być używane przez jakiś późniejszy obiekt wyjątku lub rejestrowanie (np. informacje dotyczące czasu / okoliczności, w których „oryginalna” nie utworzona kopia został zbudowany)
  • wykonać bardziej wydajną kopię niektórych danych: np. obiekty mogą mieć np. unordered_mapczłonka, ale publicznie tylko ujawniają begin()i end()iteratory - z bezpośrednim dostępem do size()ciebie można reserveby przyspieszyć kopiowanie; Co gorsza, jeśli tylko oni wystawiać at()i insert()a inaczej throw....
  • skopiuj odniesienia z powrotem do obiektów nadrzędnych / koordynacyjnych / zarządzających, które mogą być nieznane lub przeznaczone tylko do zapisu dla kodu klienta
Tony Delroy
źródło
2
Myślę, że największym „dlaczego” jest to, że sprawdzenie, czy za this == otherkażdym razem, gdy uzyskujesz dostęp other.x, byłoby ogromnym narzutem w czasie wykonywania, gdyby modyfikatory dostępu działały na poziomie obiektu.
aioobe
2
@aioobe Myślę, że Twoja odpowiedź powinna być o wiele bardziej widoczna. Odpowiedź Whilela Tony'ego jest naprawdę dobra i koncepcyjna. Gdybym był bukmacherem, założę się, że twoja odpowiedź jest faktycznym historycznym powodem wyboru. Jest nie tylko bardziej wydajny, ale także znacznie prostszy. To byłoby świetne pytanie dla Bjarne'a!
Nir Friedman
Oznaczyłem twoją odpowiedź, ponieważ wyjaśnia tło;)
demonking
@demonking, myślę, że powody podane w tej odpowiedzi wyjaśniają, dlaczego wygodnie jest pozwolić, aby prywatne dane były otwarte dla innych obiektów. Jednak modyfikatory dostępu nie mają na celu „jawności” danych. Mają raczej na celu wystarczająco zamknięcie danych do hermetyzacji. (Z punktu widzenia wygody byłoby jeszcze lepiej, gdyby zmienne prywatne były publiczne!) Zaktualizowałem odpowiedź o sekcję, która moim zdaniem lepiej opisuje faktyczne przyczyny .
aioobe
@aioobe: stare komentarze, ale w każdym razie ... "sprawdzaj, czy za this == otherkażdym razem, gdy uzyskujesz dostęp other.x" - pomija sedno - gdyby other.xbyło akceptowane tylko w czasie wykonywania, gdy jest równoważne this.x, nie byłoby other.xpo pierwsze dużo pisania wskaźników ; kompilator może równie dobrze zmusić cię do napisania if (this == other) ...this.x...tego, co zamierzałeś zrobić. Twoja koncepcja „wygody (nawet bardziej, jeśli zmienne prywatne byłyby publiczne)” również mija się z celem - sposób zdefiniowania Standardu jest wystarczająco restrykcyjny, aby umożliwić właściwe hermetyzację, ale nie jest niepotrzebnie niewygodny.
Tony Delroy,
108

Modyfikatory dostępu działają na poziomie klasy , a nie obiektu .

Oznacza to, że dwa obiekty tej samej klasy mogą wzajemnie uzyskiwać dostęp do prywatnych danych.

Czemu:

Przede wszystkim ze względu na wydajność. Sprawdzanie, czy za this == otherkażdym razem, gdy uzyskujesz dostęp other.x, wymagałoby sprawdzenia, czy modyfikatory dostępu działały na poziomie obiektu, byłoby nie do pominięcia kosztem czasu wykonania .

Jest to również trochę logiczne semantycznie, jeśli myślisz o tym w kategoriach zakresu: „Jak dużą część kodu muszę pamiętać, modyfikując zmienną prywatną?” - Musisz pamiętać o kodzie całej klasy, a to jest ortogonalne, do których obiekty istnieją w czasie wykonywania.

Jest to niezwykle wygodne podczas pisania konstruktorów kopiujących i operatorów przypisania.

aioobe
źródło
35

Możesz uzyskać dostęp do prywatnych członków klasy z poziomu klasy, nawet z innej instancji.

Alexander Rafferty
źródło
10

Aby zrozumieć odpowiedź, chciałbym przypomnieć kilka pojęć.

  1. Bez względu na to, ile obiektów utworzysz, w pamięci jest tylko jedna kopia jednej funkcji dla tej klasy. Oznacza to, że funkcje są tworzone tylko raz. Jednak zmienne są oddzielne dla każdej instancji klasy.
  2. this wskaźnik jest przekazywany do każdej funkcji, gdy jest wywoływana.

Teraz to z powodu this wskaźnikowi funkcja jest w stanie zlokalizować zmienne tej konkretnej instancji. bez względu na to, czy jest prywatny czy publiczny. można uzyskać do niego dostęp wewnątrz tej funkcji. Teraz, jeśli przekażemy wskaźnik do innego obiektu tej samej klasy. używając tego drugiego wskaźnika będziemy mogli uzyskać dostęp do prywatnych członków.

Mam nadzieję, że to odpowiada na twoje pytanie.

Ali Zaib
źródło
6

Konstruktor kopiujący jest funkcją składową klasy i jako taki ma dostęp do elementów składowych danych klasy, nawet tych zadeklarowanych jako „prywatne”.

Bojan Komazec
źródło
3
Jako ktoś, kto zna język, rozumiem, co masz na myśli. Gdybym jednak nie znał języka, pomyślałbym, że po prostu powtarzasz mi pytanie.
San Jacinto