Istnieje kilka problemów z odbiciem w C ++.
Jest dużo pracy do dodania, a komitet C ++ jest dość konserwatywny i nie spędza czasu na radykalnie nowych funkcjach, chyba że są pewni, że to się opłaci. (Pojawiła się sugestia dodania systemu modułów podobnego do zestawów .NET i chociaż wydaje mi się, że istnieje ogólna zgoda co do tego, że byłoby miło mieć, nie jest to obecnie ich priorytetem i została odsunięta z powrotem dopiero po pewnym czasie C ++ 0x. Motywacją dla tej funkcji jest pozbycie się #include
systemu, ale umożliwiłby także przynajmniej niektóre metadane).
Nie płacisz za to, czego nie używasz. To jedna z podstawowych zasad projektowania C ++. Dlaczego mój kod powinien przenosić metadane, jeśli nigdy ich nie potrzebuję? Ponadto dodanie metadanych może uniemożliwić optymalizację kompilatora. Dlaczego powinienem płacić ten koszt w kodzie, jeśli nigdy nie potrzebuję tych metadanych?
Co prowadzi nas do kolejnej ważnej kwestii: C ++ daje bardzo niewiele gwarancji dotyczących skompilowanego kodu. Kompilator może robić, co tylko zechce, pod warunkiem, że oczekiwana jest wynikowa funkcjonalność. Na przykład, twoje zajęcia nie muszą
tam być . Kompilator może je optymalizować, wstawiać wszystko, co robią, i często to właśnie robi, ponieważ nawet prosty kod szablonu ma tendencję do tworzenia całkiem wielu instancji szablonów. Standardowa biblioteka C ++ polega na tej agresywnej optymalizacji. Funkcje działają tylko wtedy, gdy można zoptymalizować narzut związany z tworzeniem i niszczeniem obiektu.
operator[]
na wektorze jest porównywalny pod względem wydajności tylko z indeksowaniem macierzy surowych, ponieważ cały operator można wstawić, a tym samym całkowicie usunąć ze skompilowanego kodu. C # i Java dają wiele gwarancji na temat wyjścia kompilatora. Jeśli zdefiniuję klasę w języku C #, klasa ta będzie istnieć w wynikowym zestawie. Nawet jeśli go nigdy nie używam. Nawet jeśli wszystkie wywołania jego funkcji członkowskich mogą być wstawiane. Klasa musi tam być, aby odbicie mogło ją znaleźć. Część tego jest złagodzona przez kompilację C # do kodu bajtowego, co oznacza, że kompilator JIT możeusuń definicje klas i funkcje wstawiane, jeśli chce, nawet jeśli początkowy kompilator C # nie może. W C ++ masz tylko jeden kompilator i musi on generować wydajny kod. Jeśli pozwolono ci na sprawdzenie metadanych pliku wykonywalnego C ++, można oczekiwać, że zobaczysz każdą zdefiniowaną przez siebie klasę, co oznacza, że kompilator będzie musiał zachować wszystkie zdefiniowane klasy, nawet jeśli nie są one konieczne.
A potem są szablony. Szablony w C ++ nie przypominają ogólnych w innych językach. Każda instancja szablonu tworzy
nowy typ. std::vector<int>
jest całkowicie odrębną klasą od
std::vector<float>
. To składa się na wiele różnych typów w całym programie. Co powinno zobaczyć nasze odbicie? Szablon std::vector
? Ale jak to zrobić, skoro jest to konstrukcja kodu źródłowego, która nie ma znaczenia w czasie wykonywania? Musiałby zobaczyć osobne klasy
std::vector<int>
i
std::vector<float>
. A
std::vector<int>::iterator
i
std::vector<float>::iterator
, sam dlaconst_iterator
i tak dalej. Po wejściu w metaprogramowanie szablonów szybko tworzy się setki szablonów, z których wszystkie zostają ponownie wstawione i usunięte przez kompilator. Nie mają one żadnego znaczenia, chyba że jako część metaprogramu podczas kompilacji. Czy wszystkie setki klas powinny być widoczne do refleksji? Musieliby, bo inaczej nasza refleksja byłaby bezużyteczna, gdyby nawet nie gwarantowała, że zdefiniowane przeze mnie klasy rzeczywiście tam będą . Problemem ubocznym jest to, że klasa szablonów nie istnieje, dopóki nie zostanie utworzona instancja. Wyobraź sobie program, który używa std::vector<int>
. Czy nasz system refleksji powinien widzieć std::vector<int>::iterator
? Z jednej strony na pewno byś się tego spodziewał. Jest to ważna klasa i jest zdefiniowana w kategoriach std::vector<int>
, co robiistnieją w metadanych. Z drugiej strony, jeśli program nigdy nie używa tego szablonu klasy iteratora, jego typ nigdy nie zostanie utworzony, a więc kompilator nie wygeneruje klasy w pierwszej kolejności. I jest za późno, aby go utworzyć w czasie wykonywania, ponieważ wymaga on dostępu do kodu źródłowego.
- I wreszcie, odbicie nie jest tak istotne w C ++, jak w C #. Powodem jest znowu, metaprogramowanie szablonu. Nie jest w stanie rozwiązać wszystkiego, ale w wielu przypadkach, w których można by się zastanowić, można napisać metaprogram, który robi to samo w czasie kompilacji.
boost::type_traits
jest prostym przykładem. Chcesz wiedzieć o typie
T
? Sprawdź to type_traits
. W języku C # będziesz musiał łowić ryby po swoim typie za pomocą odbicia. Refleksja nadal byłaby przydatna w niektórych rzeczach (głównym zastosowaniem, którego widzę, którego metaprogramowania nie można łatwo zastąpić, jest automatyczny generowanie kodu serializacji), ale wiązałby się to z poważnymi kosztami dla C ++ i po prostu nie jest konieczne tak często, jak to jest jest w innych językach.
Edycja:
W odpowiedzi na komentarze:
cdleary: Tak, symbole debugowania robią coś podobnego, ponieważ przechowują metadane dotyczące typów używanych w pliku wykonywalnym. Ale cierpią również z powodu problemów, które opisałem. Jeśli kiedykolwiek próbowałeś debugować kompilację wydania, będziesz wiedział, co mam na myśli. Istnieją duże logiczne luki, w których utworzono klasę w kodzie źródłowym, która została usunięta w końcowym kodzie. Jeśli użyjesz refleksji do czegoś pożytecznego, potrzebujesz jej bardziej niezawodnej i spójnej. W tej chwili typy znikają i znikają prawie za każdym razem, gdy kompilujesz. Zmieniasz mały detal, a kompilator decyduje się na zmianę, które typy zostaną wstawione, a które nie, w odpowiedzi. Jak wydobyć z tego cokolwiek przydatnego, gdy „ nie masz nawet gwarancji, że najbardziej odpowiednie typy będą reprezentowane w twoich metadanych? Typ, którego szukałeś, mógł znajdować się w ostatniej wersji, ale teraz go nie ma. A jutro ktoś sprawdzi niewielką niewinną zmianę na małą niewinną funkcję, co sprawi, że ten typ będzie na tyle duży, że nie zostanie całkowicie uwypuklony, więc wróci z powrotem. Nadal jest to przydatne w przypadku symboli debugowania, ale niewiele więcej. Nienawidzę próbować wygenerować kod serializacji dla klasy na tych warunkach. ale niewiele więcej. Nienawidzę próbować wygenerować kod serializacji dla klasy na tych warunkach. ale niewiele więcej. Nienawidzę próbować wygenerować kod serializacji dla klasy na tych warunkach.
Evan Teran: Oczywiście te problemy można rozwiązać. Ale to wraca do mojego punktu # 1. Zajmie to dużo pracy, a komitet C ++ ma wiele rzeczy, które ich zdaniem są ważniejsze. Czy korzyść z posiadania ograniczonej refleksji (i byłaby ograniczona) w C ++ jest naprawdę wystarczająco duża, aby uzasadnić skupienie się na tym kosztem innych funkcji? Czy naprawdę jest ogromna korzyść z dodawania funkcji do podstawowego języka, który można już (głównie) zrobić za pomocą bibliotek i preprocesorów, takich jak QT? Być może, ale potrzeba jest o wiele mniej pilna niż gdyby takie biblioteki nie istniały. Sądzę jednak, że jeśli chodzi o konkretne sugestie, niedopuszczenie go do szablonów uczyni go całkowicie bezużytecznym. Nie można na przykład użyć refleksji na temat standardowej biblioteki. Jakiego rodzaju refleksja by niestd::vector
? Szablony są ogromną częścią C ++. Funkcja, która nie działa na szablonach, jest w zasadzie bezużyteczna.
Ale masz rację, można wprowadzić jakąś formę refleksji. Ale to byłaby poważna zmiana w języku. Tak jak obecnie, typy są wyłącznie konstrukcjami czasu kompilacji. Istnieją na korzyść kompilatora i nic więcej. Gdy kod został skompilowany, nie mają żadnego zajęcia. Jeśli się rozciągniesz, możesz argumentować, że funkcje nadal istnieją, ale tak naprawdę wszystko to zawiera wiele instrukcji asemblera skoku i wiele stosów push / pop. Dodając takie metadane, nie ma wiele do zrobienia.
Ale, jak powiedziałem, istnieje propozycja zmian w modelu kompilacji, dodawanie niezależnych modułów, przechowywanie metadanych dla wybranych typów, pozwalanie innym modułom na odwoływanie się do nich bez konieczności mieszania się z #include
s. To dobry początek i, szczerze mówiąc, jestem zaskoczony, że komitet standardowy nie tylko wyrzucił propozycję bycia zbyt dużą zmianą. Więc może za 5-10 lat? :)
export
ivector<bool>
.typeinfo
jestname()
funkcja musi zwrócić imię, które zostało wpisane w przez programistę, a nie coś niezdefiniowane. Podaj nam także stringfikator dla enumeratorów. Jest to w rzeczywistości kluczowe dla serializacji / deserializacji, pomagając w tworzeniu fabryk itp.Refleksja wymaga przechowywania niektórych metadanych dotyczących typów w miejscach, w których można wyszukiwać. Ponieważ C ++ kompiluje się do natywnego kodu maszynowego i ulega znacznym zmianom z powodu optymalizacji, widok aplikacji na wysokim poziomie jest prawie całkowicie zagubiony w procesie kompilacji, w związku z tym nie będzie możliwe sprawdzenie go w czasie wykonywania. Java i .NET używają bardzo wysokiego poziomu reprezentacji w kodzie binarnym dla maszyn wirtualnych, umożliwiając ten poziom refleksji. W niektórych implementacjach C ++ istnieje jednak coś, co nazywa się informacją typu czasu wykonania (RTTI), którą można uznać za uproszczoną wersję odbicia.
źródło
Wszystkie języki nie powinny próbować uwzględniać wszystkich funkcji każdego innego języka.
C ++ jest zasadniczo bardzo wyrafinowanym asemblerem makr. NIE jest to (w tradycyjnym sensie) język wysokiego poziomu, taki jak C #, Java, Objective-C, Smalltalk itp.
Dobrze jest mieć różne narzędzia do różnych zadań. Jeśli mamy tylko młotki, wszystko będzie wyglądać jak gwoździe itp. Posługiwanie się językami skryptowymi jest przydatne do niektórych zadań, a odblaskowe języki OO (Java, Obj-C, C #) są przydatne do innej klasy zadań i super -wydajne, pozbawione kości języki zbliżone do maszyny są przydatne w jeszcze innej klasie zadań (C ++, C, asembler).
C ++ wykonuje niesamowitą pracę polegającą na rozszerzeniu technologii Asemblera na niewiarygodne poziomy zarządzania złożonością i abstrakcjach, dzięki czemu programowanie jest większe, bardziej złożone zadania są znacznie łatwiejsze dla ludzi. Ale niekoniecznie jest to język, który najlepiej nadaje się dla tych, którzy podchodzą do swojego problemu ze ściśle wysokiego poziomu (Lisp, Smalltalk, Java, C #). Jeśli potrzebujesz języka z tymi funkcjami, aby jak najlepiej wdrożyć rozwiązanie swoich problemów, dziękuj tym, którzy stworzyli takie języki, z których wszyscy możemy korzystać!
Ale C ++ jest dla tych, którzy z jakichkolwiek powodów muszą mieć silną korelację między swoim kodem a działaniem maszyny bazowej. Niezależnie od tego, czy jest to wydajność, czy programowanie sterowników urządzeń, czy interakcja z usługami systemu operacyjnego niższego poziomu, czy cokolwiek innego, C ++ jest bardziej odpowiedni do tych zadań.
C #, Java, Objective-C wszystkie wymagają znacznie większego, bogatszego systemu wykonawczego do obsługi ich wykonywania. Środowisko wykonawcze należy dostarczyć do systemu, o którym mowa, wstępnie zainstalowane w celu obsługi działania oprogramowania. Tę warstwę należy utrzymywać dla różnych systemów docelowych, dostosowanych przez NIEKTÓRY JĘZYK, aby działał na tej platformie. I ta środkowa warstwa - ta warstwa adaptacyjna między systemem operacyjnym hosta a twoim kodem - środowisko wykonawcze, prawie zawsze jest napisana w języku takim jak C lub C ++, gdzie wydajność jest na pierwszym miejscu, gdzie zrozumiałe jest dokładne zrozumienie dokładnej interakcji między oprogramowaniem a sprzętem zrozumiany i zmanipulowany do maksymalnego wzmocnienia.
Uwielbiam Smalltalk, Objective-C i posiadanie bogatego systemu wykonawczego z refleksją, metadanymi, wyrzucaniem elementów bezużytecznych itp. Można wykorzystać niesamowity kod, aby skorzystać z tych udogodnień! Ale to po prostu wyższa warstwa na stosie, warstwa, która musi spoczywać na niższych warstwach, które same muszą ostatecznie oprzeć się na systemie operacyjnym i sprzęcie. I zawsze będziemy potrzebować języka, który najlepiej nadaje się do budowania tej warstwy: C ++ / C / Asembler.
Dodatek: C ++ 11/14 nadal rozszerza możliwości C ++ do obsługi abstrakcyjnych poziomów i systemów. Wątki, synchronizacja, precyzyjne modele pamięci, bardziej precyzyjne abstrakcyjne definicje maszyn umożliwiają programistom C ++ osiągnięcie wielu abstrakcyjnych poziomów wysokiego poziomu, nad którymi niektóre z tych tylko języków wysokiego poziomu posiadały wyłączną domenę, jednocześnie zapewniając bliskość do wydajność metalu i doskonała przewidywalność (tj. minimalne podsystemy czasu wykonywania). Być może narzędzia do refleksji zostaną selektywnie włączone w przyszłej wersji C ++ dla tych, którzy tego chcą - a może biblioteka zapewni takie usługi środowiska uruchomieniowego (może jest teraz jedna, lub początki jednej w fazie ulepszenia?).
źródło
Jeśli naprawdę chcesz zrozumieć decyzje projektowe dotyczące C ++, znajdź kopię The Annotated C ++ Reference Manual autorstwa Ellis i Stroustrup. NIE jest na bieżąco z najnowszym standardem, ale przechodzi przez pierwotny standard i wyjaśnia, jak rzeczy działają, a często, w jaki sposób je osiągnęły.
źródło
Refleksja dla języków, które go mają, dotyczy tego, ile kodu źródłowego kompilator jest skłonny pozostawić w kodzie obiektu, aby umożliwić odbicie, oraz ile maszyn analizy jest dostępnych do interpretacji tych odbitych informacji. O ile kompilator nie utrzymuje całego kodu źródłowego, refleksja będzie ograniczona w zakresie możliwości analizy dostępnych faktów na temat kodu źródłowego.
Kompilator C ++ nie utrzymuje niczego (no, ignorując RTTI), więc nie dostaniesz refleksji w języku. (Kompilatory Java i C # przechowują tylko klasy, nazwy metod i typy zwracane, więc otrzymujesz trochę danych refleksyjnych, ale nie możesz sprawdzać wyrażeń ani struktury programu, a to oznacza, że nawet w tych językach z włączoną funkcją refleksji informacje, które można uzyskać, są dość rzadkie, w związku z czym naprawdę nie można przeprowadzić dużej analizy).
Ale możesz wyjść poza język i uzyskać pełne możliwości refleksji. Odpowiedź na inną dyskusję dotyczącą przepełnienia stosu na odbicie w C omawia to.
źródło
Refleksja może być i była wcześniej implementowana w c ++.
Nie jest to natywna funkcja języka C ++, ponieważ ma duże koszty (pamięć i szybkość), których domyślnie nie powinien ustawiać język - język jest zorientowany na „maksymalną wydajność domyślnie”.
Ponieważ nie powinieneś płacić za to, czego nie potrzebujesz, i jak sam twierdzisz, jest to potrzebne bardziej w edytorach niż w innych aplikacjach, dlatego powinno być implementowane tylko tam, gdzie jest to potrzebne, a nie „wymuszane” na cały kod ( nie potrzebujesz refleksji nad wszystkimi danymi, z którymi będziesz pracować w edytorze lub innej podobnej aplikacji).
źródło
Powodem, dla którego C ++ nie ma odzwierciedlenia, jest to, że wymagałoby to od kompilatorów dodania informacji o symbolach do plików obiektowych, takich jak elementy należące do typu klasy, informacje o elementach, o funkcjach i wszystkim. Zasadniczo spowodowałoby to, że pliki dołączane byłyby bezużyteczne, ponieważ informacje przesyłane przez deklaracje byłyby następnie odczytywane z tych plików obiektowych (wówczas modułów). W C ++ definicja typu może występować wiele razy w programie poprzez dołączenie odpowiednich nagłówków (pod warunkiem, że wszystkie te definicje są takie same), więc należałoby zdecydować, gdzie umieścić informacje o tym typie, tak jak nazwać jeden komplikacja tutaj. Kolejną mocną stroną jest agresywna optymalizacja przeprowadzona przez kompilator C ++, który może zoptymalizować dziesiątki instancji szablonów klas. Jest to możliwe, ale ponieważ C ++ jest kompatybilny z C,
źródło
Istnieje mnóstwo przypadków użycia refleksji w C ++, których nie można odpowiednio rozwiązać za pomocą konstruktów czasu kompilacji, takich jak metaprogramowanie szablonu.
N3340 proponuje bogate wskaźniki jako sposób na wprowadzenie refleksji w C ++. Między innymi dotyczy to kwestii nieopłacania funkcji, chyba że z niej skorzystasz.
źródło
Według Alistair Cockburn nie można zagwarantować podsieci w środowisku odbijającym światło .
Odbicie jest bardziej odpowiednie dla ukrytych systemów pisania. W C ++ wiesz, jaki masz typ i wiesz, co możesz z nim zrobić.
źródło
Odbicie może być opcjonalne, podobnie jak dyrektywa preprocesora. Coś jak
#pragma enable reflection
W ten sposób możemy mieć to, co najlepsze z obu światów, bez tego pragmatyczne biblioteki byłyby tworzone bez refleksji (bez omówionych kosztów ogólnych), wtedy indywidualny programista będzie zależał od tego, czy chcą szybkości czy łatwości użytkowania.
źródło
Jeśli C ++ mógłby mieć:
const
modyfikatoraconst
modyfikatoraWystarczyłoby to do stworzenia bardzo łatwych w użyciu bibliotek w centrum typowego przetwarzania danych, które jest tak powszechne we współczesnych aplikacjach internetowych i bazach danych (wszystkie orms, mechanizmy przesyłania wiadomości, parsery xml / json, serializacja danych itp.).
Na przykład podstawowe informacje obsługiwane przez
Q_PROPERTY
makro (część Qt Framework) http://qt.nokia.com/doc/4.5/properties.html rozszerzone o metody klasowe e) - byłyby niezwykle korzystne dla C ++ i społeczność oprogramowania w ogóle.Z pewnością refleksja, o której mówię, nie obejmowałaby znaczenia semantycznego ani bardziej złożonych zagadnień (takich jak komentarze wierszy kodu źródłowego, analiza przepływu danych itp.) - ale nie sądzę, aby były one częścią standardu językowego.
źródło
kilka dobrych linków do refleksji w C ++ właśnie znalazłem:
Dokument roboczy standardu C ++: Aspekty refleksji w C ++
Prosty przykład refleksji przy użyciu szablonów
źródło
Odbicie w C ++, uważam, że jest niezwykle ważne, jeśli C ++ ma być używany jako język dostępu do bazy danych, obsługi sesji WWW / HTTP i rozwoju GUI. Brak refleksji uniemożliwia ORM (takie jak Hibernacja lub LINQ), parsery XML i JSON, które powodują instancje klas, serializację danych i wiele innych problemów (w których początkowo dane beztypowe muszą być użyte do utworzenia instancji klasy).
Przełącznik czasowy kompilacji dostępny dla twórcy oprogramowania podczas procesu kompilacji może być wykorzystany do wyeliminowania problemu „płacisz za to, czego używasz”.
I programista oprogramowania nie potrzebuje refleksji, aby odczytać dane z portu szeregowego - wtedy dobrze nie używaj przełącznika. Ale jako programista baz danych, który chce nadal używać C ++, jestem ciągle wprowadzany w fazę z okropnym, trudnym do utrzymania kodem, który odwzorowuje dane między elementami danych a konstrukcjami bazy danych.
Ani serializacja Boost, ani żaden inny mechanizm tak naprawdę nie rozwiązuje problemu - musi to zrobić kompilator - a kiedy to nastąpi, C ++ będzie ponownie poszukiwany w szkołach i używany w oprogramowaniu zajmującym się przetwarzaniem danych
Dla mnie ten problem nr 1 (a naiwne prymitywy wątków to problem nr 2).
źródło
Jest to w zasadzie dlatego, że jest to „dodatek opcjonalny”. Wiele osób wybiera C ++ zamiast języków takich jak Java i C #, aby mieć większą kontrolę nad wyjściem kompilatora, np. Mniejszym i / lub szybszym programem.
Jeśli zdecydujesz się dodać odbicie, dostępne są różne rozwiązania .
źródło