Dlaczego umieszczamy funkcje członka prywatnego w nagłówkach?

16

Odpowiedź na pytanie, dlaczego umieszczamy prywatne zmienne składowe w nagłówkach C ++, jest taka, że ​​rozmiar klasy musi być znany w punktach, w których deklarowane są instancje, aby kompilator mógł wygenerować kod, który odpowiednio porusza się po stosie.

Dlaczego musimy umieszczać prywatnych członków w nagłówkach?

Ale czy istnieje jakikolwiek powód do deklarowania funkcji prywatnych w definicji klasy?

Alternatywą byłby zasadniczo idiom pimpl, ale bez zbędnej pośredniczości.

Czy ta funkcja języka jest czymś więcej niż błędem historycznym?

Prakseolityczny
źródło

Odpowiedzi:

11

Prywatne funkcje składowe mogą być virtual, a we wspólnych implementacjach C ++ (które używają vtable) wszyscy klienci klasy muszą znać określoną kolejność i liczbę funkcji wirtualnych. Ma to zastosowanie, nawet jeśli jest co najmniej jedna z wirtualnych funkcji członka private.

Może się wydawać, że to jest jak „postawienie wózka przed koniem”, ponieważ opcje implementacji kompilatora nie powinny wpływać na specyfikację języka. Jednak w rzeczywistości sam język C ++ został opracowany w tym samym czasie, co działająca implementacja ( Cfront ), która korzystała z vtables.

Greg Hewgill
źródło
4
„wybory implementacji nie powinny wpływać na język” to prawie dokładne przeciwieństwo mantry C ++. Specyfikacja nie wymaga żadnej konkretnej implementacji, ale istnieje wiele reguł opracowanych specjalnie w celu spełnienia wymagań szczególnie wydajnego wyboru implementacji.
Ben Voigt
Brzmi to bardzo prawdopodobne. Czy jest na to źródło?
Praxeolitic
@Praxeolitic: Możesz przeczytać o wirtualnej tabeli metod .
Greg Hewgill
1
@Praxeolitic - znajdź starą kopię „Annotated C ++ Refernce Manual” ( stroustrup.com/arm.html ) - autorzy mówią o różnych szczegółach implementacji i o tym, jak ukształtowała język.
Michael Kohne
Nie jestem pewien, czy to naprawdę odpowiada na pytanie. Dla mnie dotyczy to tylko jednego konkretnego aspektu - aczkolwiek dobrze - i pozostawia resztę: Dlaczego musimy umieszczać nie-wirtualne funkcje prywatne w nagłówkach? Jasne, czysta konsekwencja / prostota to jeden argument - ale idealnie mielibyśmy również mechanistyczne i / lub filozoficzne uzasadnienie. Dodałem więc odpowiedź, która moim zdaniem wyjaśnia to jako bardzo przemyślany wybór projektu z bardzo korzystnymi efektami.
underscore_d
13

Jeśli zezwoliłeś, aby metody były dodawane do klasy poza jej definicją, mogą być dodawane w dowolnym miejscu , w dowolnym pliku, przez kogokolwiek.

To natychmiast dałoby wszystkim kodom klienta trywialny dostęp do prywatnych i chronionych członków danych.

Po zakończeniu definicji klasy nie ma możliwości oznaczenia niektórych plików jako specjalnie pobłogosławionych przez autora, aby je rozszerzyć - są tylko płaskie jednostki tłumaczeniowe. Zatem jedynym rozsądnym sposobem poinformowania kompilatora, że ​​określony zestaw metod jest oficjalny lub pobłogosławiony przez autora klasy, jest zadeklarowanie ich w klasie.


Zauważ, że mamy bezpośredni dostęp do pamięci w C ++, co oznacza, że ​​generalnie trywialne jest tworzenie typu cienia z tym samym układem pamięci co klasa, dodawanie własnych metod (lub po prostu upublicznianie wszystkich danych) i reinterpret_cast. Albo mogę znaleźć kod twojej prywatnej funkcji lub go zdemontować. Lub wyszukaj adres funkcji w tablicy symboli i wywołaj lub bezpośrednio.

Te specyfikatory dostępu nie próbują zapobiegać tym atakom, ponieważ nie jest to możliwe. Wskazują tylko, w jaki sposób ma być używana klasa.

Nieprzydatny
źródło
1
Definicja klasy może odnosić się do encji kontenera funkcji ukrytej przed kodem klienta. Alternatywnie można to odwrócić, a pełna tradycyjna definicja klasy ukryta przed kodem klienta może oznaczać widoczny interfejs opisujący tylko członków publicznych i ujawniający jego rozmiar.
Praxeolitic
1
Jasne, ale model kompilacji nie zapewnia rozsądnego sposobu, aby zatrzymać kod klienta interpretujący własną wersję ukrytego kontenera lub inny widoczny interfejs z dodatkowymi akcesoriami.
Bezużyteczne
To dobra uwaga, ale czy nie jest to prawda w przypadku definicji wszystkich funkcji składowych, których nie ma w nagłówku?
Praxeolitic
1
Jednak wszystkie funkcje składowedeklarowane wewnątrz klasy. Chodzi o to, że nie można dodawać nowych funkcji składowych, które nie zostały przynajmniej zadeklarowane w definicji klasy (a reguła jednej definicji zapobiega wielu definicjom).
Bezużyteczne
A co z regułą jednego kontenera funkcji prywatnych?
Praxeolitic
8

Przyjęta odpowiedź wyjaśnia to w przypadku wirtualnych funkcji prywatnych, ale to odpowiada tylko na jeden konkretny aspekt pytania, który jest znacznie bardziej ograniczony niż to, o co pytał PO. Tak więc musimy przeformułować: Dlaczego musimy zadeklarować nie-wirtualne funkcje prywatne w nagłówkach?

Inna odpowiedź przywołuje fakt, że klasy muszą być zadeklarowane w jednym bloku - po czym są zapieczętowane i nie można do nich dodawać. To właśnie robisz, pomijając deklarację prywatnej metody w nagłówku, a następnie próbując zdefiniować ją gdzie indziej. Niezły punkt. Dlaczego niektórzy użytkownicy tej klasy powinni mieć możliwość rozszerzenia tego w sposób, którego inni użytkownicy nie mogą zaobserwować? Metody prywatne są jego częścią i nie są z tego wyłączone. Ale pytasz, dlaczego są uwzględnione, i wydaje się to trochę tautologiczne. Dlaczego użytkownicy klas muszą o nich wiedzieć? Gdyby nie były widoczne, użytkownicy nie mogliby ich dodać i hej presto.

Chciałem więc udzielić odpowiedzi, która zamiast domyślnie uwzględniać metody prywatne, zapewnia określone punkty na korzyść ich widoczności dla użytkowników. Mechanistyczny powód nie-wirtualnych funkcji prywatnych wymagających publicznej deklaracji podano w GotW # 100 Herb Suttera na temat idiomu Pimpl w ramach jego uzasadnienia. Nie będę tu mówił o Pimplu, bo jestem pewien, że wszyscy o tym wiemy. Ale oto odpowiedni fragment:

W C ++, gdy cokolwiek zmienia się w definicji klasy pliku nagłówkowego, wszyscy użytkownicy tej klasy muszą zostać ponownie skompilowani - nawet jeśli jedyną zmianą były prywatne elementy klasy, do których użytkownicy klasy nawet nie mają dostępu. Wynika to z faktu, że model kompilacji C ++ opiera się na włączeniu tekstu, a ponieważ C ++ zakłada, że ​​wywołujący wiedzą dwie główne rzeczy na temat klasy, na które mogą mieć wpływ członkowie prywatni:

  • Rozmiar i układ : [członków i funkcji wirtualnych - oczywiste i doskonałe pod względem wydajności, ale nie dlatego, dlaczego tu jesteśmy]
  • Funkcje : Kod wywołujący musi być w stanie rozstrzygać wywołania funkcji członka klasy, w tym niedostępnych funkcji prywatnych, które przeciążają się funkcjami nieprywatnymi - jeśli funkcja prywatna lepiej pasuje, kod wywołujący nie będzie mógł się skompilować. (C ++ podjął świadomą decyzję projektową, aby wykonać rozwiązanie problemu przeciążenia przed sprawdzeniem dostępności ze względów bezpieczeństwa. Na przykład uważano, że zmiana dostępności funkcji z prywatnej na publiczną nie powinna zmieniać znaczenia legalnego kodu wywołującego).

Sutter jest oczywiście niezwykle wiarygodnym źródłem jako członek Komitetu, więc zna „świadomą decyzję projektową”, kiedy ją widzi. Pomysł wymagający publicznego zadeklarowania prywatnych metod jako sposobu uniknięcia późniejszej zmienionej semantyki lub przypadkowo uszkodzonej dostępności jest prawdopodobnie najbardziej przekonującym uzasadnieniem. Na szczęście, ponieważ cała ta sprawa wydawała się wcześniej bezcelowa!

podkreślenie_d
źródło
Ale pytasz dlaczego, a ta odpowiedź wydaje się tautologiczna. Ponownie, dlaczego użytkownicy klasy muszą o nich wiedzieć? ” Nie, to nie jest tautologiczna. Użytkownicy niekoniecznie „muszą o nich wiedzieć”. To, co musi się zdarzyć, to to, że musisz być w stanie uniemożliwić ludziom przedłużanie zajęć bez zgody autora zajęć. Tak więc wszyscy tacy członkowie muszą zostać zadeklarowani z góry. To, że powoduje, że użytkownicy wiedzą o prywatnych członkach, jest jedynie konieczną konsekwencją.
Nicol Bolas,
1
@NicolBolas Na pewno. To chyba nie było zbyt dobre sformułowanie z mojej strony. Miałem na myśli, że odpowiedź wyjaśnia tylko widoczność metod prywatnych jako konsekwencję (bardzo ważnej) reguły obejmującej wiele rzeczy, a nie uzasadnienie konkretnej widoczności metod prywatnych. Oczywiście, prawdziwą odpowiedzią jest kombinacja Bezużytecznej, zgrzytacza i mojej. Chcę tylko przedstawić inną perspektywę, której jeszcze tu nie było.
underscore_d
4

Są dwa powody, aby to zrobić.

Po pierwsze, uświadom sobie, że specyfikator dostępu jest przeznaczony dla kompilatora i nie ma znaczenia w czasie wykonywania. Dostęp do członka prywatnego poza zakresem jest błędem kompilacji .

Zwięzłość

Rozważ funkcję krótką, jedną lub dwie linie. Istnieje, aby ograniczyć replikację kodu w innym miejscu, co ma tę zaletę, że może zmienić sposób działania algorytmu lub cokolwiek innego w jednym miejscu zamiast w wielu (np. Zmiana algorytmu sortowania).

Czy wolałbyś mieć jedną lub dwie linie w nagłówku, czy mieć prototyp funkcji i implementację? Łatwiej jest znaleźć w nagłówku, a dla krótkich funkcji o wiele bardziej gadatliwa jest osobna implementacja.

Jest jeszcze jedna ważna zaleta, którą jest ...

Funkcje wbudowane

Funkcja prywatna może być wstawiana, a to koniecznie wymaga umieszczenia jej w nagłówku. Rozważ to:

class A {
  private:
    inline void myPrivateFunction() {
      ...
    }

  public:
    inline void somePublicFunction() {
      myPrivateFunction();
      ...
    }
};

Funkcja prywatna może być zintegrowana z funkcją publiczną. Odbywa się to według uznania kompilatora, ponieważ inlinesłowo kluczowe jest technicznie sugestią , a nie wymogiem.

Społeczność
źródło
Wszystkie funkcje zdefiniowane w treści klasy są automatycznie oznaczane inline, nie ma powodu, aby używać słowa kluczowego.
Ben Voigt
@BenVoigt tak jest. Nie zdawałem sobie z tego sprawy i od dłuższego czasu używam C ++ na pół etatu. Ten język nigdy nie przestaje mnie zaskakiwać takimi małymi samorodkami.
Nie sądzę, aby metoda mogła być wstawiana w nagłówku. Musi być tylko w tej samej jednostce kompilacji. Czy istnieje przypadek, w którym sensowne są oddzielne jednostki kompilacji dla klasy?
Samuel Danielson,
@SamuelDanielson jest poprawny, ale funkcja prywatna musi znajdować się w definicji klasy. Samo pojęcie „prywatny” oznacza, że ​​należy ono do klasy. Możliwe byłoby posiadanie w .cpppliku funkcji innej niż członkowska , która jest wstawiana przez funkcje składowe zdefiniowane poza definicją klasy, ale taka funkcja nie byłaby prywatna.
Treść pytania brzmiała: „czy istnieje jakikolwiek powód do deklarowania funkcji prywatnych w definicji klasy [sic, w którym kontekst pokazuje, że OP naprawdę oznacza deklarację klasy]”. Mówisz o zdefiniowaniu funkcji prywatnych. To nie odpowiada na pytanie. @SamuelDanielson Obecnie LTO oznacza, że ​​funkcje mogą być w dowolnym miejscu w projekcie i zachowują równe szanse na bycie wprowadzonym. Jeśli chodzi o podział klasy na wiele jednostek tłumaczeniowych, najprostszym przypadkiem jest to, że klasa jest po prostu duża i chcesz podzielić ją semantycznie na wiele plików źródłowych. Jestem pewien, że tak duże klasy są odradzane, ale w każdym razie
underscore_d
2

Kolejny powód posiadania prywatnych metod w pliku nagłówkowym: Istnieją przypadki, w których publiczna metoda wbudowana nie robi nic więcej niż wywoływanie jednej lub kilku metod prywatnych. Posiadanie metod prywatnych w nagłówku oznacza, że ​​wywołanie metody publicznej może być całkowicie powiązane z rzeczywistym kodem metod prywatnych, a wstawianie nie kończy się na wywołaniu metody prywatnej. Nawet z innej jednostki kompilacji (a metody publiczne są zwykle wywoływane z różnych jednostek kompilacji).

Oczywiście istnieje również powód, dla którego kompilator nie może wykryć problemów z rozwiązaniem problemu przeciążenia, jeśli nie zna wszystkich metod, w tym prywatnych.

gnasher729
źródło
Doskonały punkt na temat wkładania! Wyobrażam sobie, że jest to szczególnie istotne dla stdlib i innych bibliotek z metodami zdefiniowanymi inline. (jeśli nie tak bardzo w przypadku kodu wewnętrznego, w którym LTO może zrobić prawie wszystko ponad granicami TU)
podkreślenie_d
0

Ma to umożliwić tym funkcjom dostęp do prywatnych członków. W przeciwnym razie i tak będziesz potrzebować friendich w nagłówku.

Jeśli jakakolwiek funkcja mogłaby uzyskać dostęp do prywatnych członków klasy, private byłoby bezużyteczne.

maniak zapadkowy
źródło
1
Może powinienem był udzielić bardziej precyzyjnego pytania - miałem na myśli, dlaczego język został zaprojektowany w ten sposób? Dlaczego tak musi być lub dlaczego ten projekt jest dobry?
Praxeolitic