Dlaczego zmienne prywatne są opisane w publicznie dostępnym pliku nagłówkowym?

11

OK, więc mam nadzieję, że jest to subiektywne pytanie dla programistów, ale proszę bardzo. Nieustannie poszerzam swoją znajomość języków i praktyk inżynierii oprogramowania ... i natknąłem się na coś, co nie ma dla mnie żadnego sensu.

W C ++ deklaracje klas zawierają private:metody i parametry w pliku nagłówkowym, który teoretycznie przekazuje się użytkownikowi, jeśli zostanie on lib.

W Objective-C @interfacerobimy prawie to samo, zmuszając cię do listy prywatnych członków (przynajmniej istnieje sposób na uzyskanie prywatnych metod w pliku implementacji).

Z tego, co mogę powiedzieć, Java i C # pozwalają na zapewnienie interfejsu / protokołu, który może zadeklarować wszystkie publicznie dostępne właściwości / metody i daje koderowi możliwość ukrycia wszystkich szczegółów implementacji w pliku implementacji.

Czemu? Enkapsulacja jest jedną z głównych zasad OOP, dlaczego w C ++ i Obj-C brakuje tej podstawowej umiejętności? Czy istnieje jakiś sprawdzony sposób obejścia Obj-C lub C ++, który ukrywa całą implementację?

Dzięki,

Stephen Furlani
źródło
4
Czuję twój ból. Uciekłem krzycząc z C ++, kiedy po raz pierwszy dodałem prywatne pole do klasy i musiałem ponownie skompilować wszystko, co z niego korzystało.
Larry Coleman,
@ Larry, nie przeszkadza mi to zbytnio, ale wydaje się, że jest to wspaniały język OO, ale nie potrafi nawet opisać poprawnie.
Stephen Furlani,
2
Nie wierz w szum. Istnieją lepsze sposoby wykonywania OO, zarówno w obozach pisania statycznego, jak i dynamicznego.
Larry Coleman,
Jest to świetny język pod pewnymi względami, ale nie ze względu na jego zdolności OO, które są szczepieniem Simuli 67 na C.
David Thornley
Powinieneś zrobić trochę programowania C. Dzięki temu będziesz lepiej rozumieć, dlaczego te języki są takie, jakie są, w tym Java C # itp.
Henry

Odpowiedzi:

6

Pytanie brzmi, czy kompilator musi wiedzieć, jak duży jest obiekt. Jeśli tak, to kompilator musi wiedzieć o członkach prywatnych, aby je policzyć.

W Javie istnieją pierwotne typy i obiekty, a wszystkie obiekty są przydzielane osobno, a zawierające je zmienne są tak naprawdę wskaźnikami. Dlatego, ponieważ wskaźnik jest obiektem o stałym rozmiarze, kompilator wie, jak dużą rzecz reprezentuje zmienna, nie znając rzeczywistego rozmiaru wskazywanego obiektu. Konstruktor obsługuje to wszystko.

W C ++ można mieć obiekty reprezentowane lokalnie lub na stercie. Dlatego kompilator musi wiedzieć, jak duży jest obiekt, aby mógł przydzielić zmienną lokalną lub tablicę.

Czasami pożądane jest podzielenie funkcjonalności klasy na interfejs publiczny i prywatny wszystko inne, i tam właśnie pojawia się technika PIMPL (Pointer to IMPLementation). Klasa będzie miała wskaźnik do prywatnej klasy implementacyjnej, a metody klasy publicznej mogą wywoływać że.

David Thornley
źródło
czy kompilator nie może spojrzeć na bibliotekę obiektów, aby uzyskać te informacje? Dlaczego te informacje nie mogą być czytelne dla komputera, a nie dla człowieka?
Stephen Furlani,
@Stephen: W momencie kompilacji niekoniecznie istnieje biblioteka obiektów. Ponieważ rozmiar obiektów wpływa na skompilowany kod, musi on być znany w czasie kompilacji, a nie tylko w czasie łączenia. Ponadto Ah może zdefiniować klasę A, Bh zdefiniować klasę B, a definicje funkcji w A.cpp używać obiektów klasy B i odwrotnie. W takim przypadku konieczne byłoby skompilowanie każdego z A.cpp i B.cpp przed drugim, jeśli wzięto rozmiar obiektu z kodu obiektowego.
David Thornley,
nie można wykonać łączenia podczas kompilacji? Przyznaję się, że nie znam szczegółów na tej głębokości, ale jeśli interfejs obiektu ujawnia jego zawartość, czy te same treści nie mogą zostać ujawnione z biblioteki lub innego komponentu odczytywalnego maszynowo? czy muszą być zdefiniowane w publicznie dostępnym pliku nagłówkowym? Rozumiem, że jest to część większości języków C, ale czy musi tak być?
Stephen Furlani,
1
@ Stephen: Łączenie można wykonać podczas kompilacji tylko wtedy, gdy istnieją wszystkie odpowiednie komponenty. Nawet wtedy byłby to skomplikowany proces, obejmujący częściową kompilację (wszystko oprócz rozmiarów obiektów), częściowe łączenie (aby uzyskać rozmiary obiektów), a następnie końcową kompilację i łączenie. Biorąc pod uwagę, że C i C ++ są stosunkowo starymi językami, to po prostu się nie wydarzyło. Prawdopodobnie ktoś mógłby zaprojektować nowy język bez plików nagłówkowych, ale nie byłby to C ++.
David Thornley,
14

Ze względu na konstrukcję C ++, aby utworzyć obiekt na stosie, kompilator musi wiedzieć, jak duży jest. Aby to zrobić, musi mieć wszystkie pola obecne w pliku nagłówka, ponieważ to wszystko, co kompilator może zobaczyć, gdy nagłówek jest dołączony.

Na przykład, jeśli zdefiniujesz klasę

class Foo {
    public int a;
    private int b;
};

to sizeof(Foo)jest sizeof(a) + sizeof(b). Jeśli istniał jakiś mechanizm oddzielający pola prywatne, nagłówek może zawierać

class Foo {
    public int a;
};

z sizeof(Foo) = sizeof(a) + ???.

Jeśli chcesz naprawdę ukryć prywatne dane, wypróbuj idiom pimpl z

class FooImpl;
class Foo {
    private FooImpl* impl;
}

w nagłówku i definicję FooImpltylko w Foopliku implementacyjnym.

Scott Wales
źródło
O. Nie miałem pojęcia, że ​​tak jest. Czy dlatego języki takie jak Java, C # i Obj-C mają „obiekty klasy”, aby mogli zapytać klasę, jak duży powinien być ten obiekt?
Stephen Furlani,
4
Spróbuj użyć pimpl, wskaźnika do prywatnej implementacji. Ponieważ wszystkie wskaźniki klas mają ten sam rozmiar, nie musisz definiować klasy implementacji, tylko ją zadeklaruj.
Scott Wales,
Myślę, że powinna to być odpowiedź zamiast komentarza.
Larry Coleman,
1

Wszystko sprowadza się do wyboru projektu.

Jeśli naprawdę chcesz ukryć prywatne szczegóły implementacji klasy C ++ lub Objective-C, to możesz podać jeden lub więcej interfejsów obsługiwanych przez klasę (czysta klasa wirtualna C ++, Objective-C @protocol) i / lub stworzyć klasę potrafi się konstruować, zapewniając statyczną metodę fabryki lub obiekt fabryki klasy.

Powodem, dla którego zmienne prywatne są ujawniane w pliku nagłówkowym / deklaracji klasy / interfejsie @, jest to, że konsument twojej klasy może potrzebować utworzyć jego nową instancję, a a new MyClass()lub [[MyClass alloc]init]w kodzie klienta potrzebuje kompilatora, aby zrozumieć, jak duża jest MyClass Obiekt ma na celu dokonanie przydziału.

Java i C # mają również swoje prywatne zmienne wyszczególnione w klasie - nie są wyjątkiem, ale IMO paradygmat interfejsu jest znacznie bardziej powszechny w tych językach. W każdym przypadku możesz nie mieć kodu źródłowego, ale w skompilowanym / bajtowym kodzie jest wystarczająca ilość metadanych, aby wydedukować te informacje. Ponieważ C ++ i Objective-C nie mają tych metadanych, jedyną opcją są rzeczywiste szczegóły interfejsu class / @. W świecie C ++ COM nie ujawniasz prywatnych zmiennych żadnych klas, ale możesz podać plik nagłówkowy - ponieważ klasa jest czysto wirtualna. Obiekt fabryki klasy jest rejestrowany w celu tworzenia rzeczywistych instancji, a niektóre metadane mają różne formy.

W C ++ i Objective-C mniej jest pracy na rozdanie pliku nagłówka w porównaniu do pisania i utrzymywania dodatkowego pliku interfejsu / protokołu. Jest to jeden z powodów, dla których tak często ujawnia się prywatne wdrożenie.

Innym powodem w C ++ są szablony - kompilator musi znać szczegóły klasy, aby wygenerować wersję tej klasy specjalizującą się w podanych parametrach. Wielkość członków klasy różni się w zależności od parametryzacji, więc musi mieć te informacje.

JBRWilkinson
źródło