Jakie są problemy z wprowadzeniem C ++ - podobnego do const?

17

Interesuje mnie idea C ++ - constnie taka konkretna realizacja (jak rzutowanie const).

Weźmy na przykład C # - brakuje C ++ - podobnie jak const, a przyczyną tego są zwykli ludzie i czas. Tutaj dodatkowo wydaje się, że zespół C # spojrzał na wykonanie C ++ const, marketing CLR i miał w tym momencie dość (zobacz dlaczego nie ma metody const member w parametrze c # i const ; dziękuję Svick). Czy jeśli pójdziemy dalej, czy jest coś jeszcze?

Czy jest coś głębszego? Weźmy na przykład dziedziczenie wielokrotne - zwykle jest postrzegane jako trudne do uchwycenia (dla użytkownika) i dlatego nie jest dodawane do języka (jak problem z diamentem).

Czy jest coś w NATURY o constto stanowić problem, że lepiej jest dla języka, aby tego uniknąć? Coś w rodzaju decyzji, czy constpowinien być głęboki czy płytki (posiadanie constkontenera oznacza, że ​​nie mogę również dodawać nowego elementu ani zmieniać istniejących elementów; co jeśli elementy są typu odniesienia)?

AKTUALIZACJA : chociaż wspominam o C #, a tym samym czyniąc perspektywę historyczną, jestem zainteresowany naturą potencjalnych problemów z constjęzykiem.

To nie jest wyzwanie dla języków. Zignoruj ​​takie czynniki, jak aktualne trendy lub popularność - interesują mnie tylko kwestie techniczne - dziękuję.

Greenoldman
źródło
3
na marginesie: wielokrotne dziedziczenie może nie być trudne do uchwycenia, ale rozdzielczość metody może stać się dość brzydka. Naiwna rozdzielczość pierwszej głębi szybko staje się nieintuicyjna („ problem z diamentem ”). C3 zamówienie rozdzielczości metoda (opublikowane '96) rozwiązuje ten problem, ale to z pewnością nie jest łatwy do zrozumienia. Dlatego zakazanie wielokrotnego dziedziczenia może często wydawać się dość rozsądny - ale prawdziwym problemem jest to, że Java wyprzedza algorytm C3, a C # zorientuje się po Javie.
amon
3
@amon Wiele osób tak naprawdę nie uważa, że ​​wielokrotne dziedziczenie to funkcja, którą chcesz mieć w pierwszej kolejności, np. twórcy języka programowania D: Jest to złożona funkcja o wartości dyskusyjnej. Bardzo trudno jest wdrożyć go w wydajny sposób, a kompilatory są podatne na wiele błędów we wdrażaniu go. Prawie całą wartością MI można obsłużyć za pomocą pojedynczego dziedziczenia w połączeniu z interfejsami i agregacją. To, co zostało, nie uzasadnia wagi wdrożenia MI. (źródło)
jcora,
2
Nawet bez wielokrotnego dziedziczenia możesz już zakodować dowolny problem 3-SAT jako metodę rozwiązywania (a dokładniej rozdzielczości przeciążenia) w języku C #, dzięki czemu jest on NP-kompletny.
Jörg W Mittag
1
@svick, dziękuję bardzo. Ponieważ nie znalazłem odpowiedzi na to pytanie, mogłem mocno go edytować, skupiając się na naturze problemu, a nie na przyczynach historycznych, ponieważ wydaje się, że 99% z nich to „czas + ludzie + stronniczość anty C ++” + CLR ”.
greenoldman

Odpowiedzi:

10

Pamiętaj, że jeśli to Ci odpowiada, ale w językach funkcjonalnych, takich jak Standard ML, domyślnie wszystko jest niezmienne. Mutacja jest wspierana przez ogólny reftyp erence. Więc intzmienna jest niezmienna, a ref intzmienna jest zmienny pojemnik na ints. Zasadniczo zmienne są rzeczywistymi zmiennymi w sensie matematycznym (nieznana, ale stała wartość), a refs są „zmiennymi” w sensie programowania imperatywnego - komórką pamięci, z której można zapisywać i odczytywać. (Lubię nazywać je przypisywalnymi ).

Myślę, że problem constjest dwojaki. Po pierwsze, C ++ nie ma funkcji wyrzucania elementów bezużytecznych, co jest konieczne, aby mieć nietrywialne trwałe struktury danych . const musi być głęboki, aby mieć jakikolwiek sens, jednak posiadanie w pełni niezmiennych wartości w C ++ jest niepraktyczne.

Po drugie, w C ++ musisz się zdecydować, consta nie zrezygnować. Ale kiedy zapomnisz o constczymś, a później to naprawisz, skończysz w sytuacji „zatruć const”, o której mowa w odpowiedzi @ RobY, gdzie constzmiana będzie kaskadowo w całym kodzie. Gdyby constbyło to ustawienie domyślne, nie zobaczyłbyś, że aplikujesz z constmocą wsteczną. Dodatkowo konieczność dodawania constwszędzie powoduje znaczny szum w kodzie.

Podejrzewam, że późniejsze języki głównego nurtu (np. Java) były silnie ukształtowane przez sukces i sposób myślenia C i C ++. W tym przypadku nawet w przypadku wyrzucania elementów bezużytecznych większość interfejsów API do zbierania języków zakłada zmienne struktury danych. Fakt, że wszystko jest zmienne i niezmienność jest postrzegany jako przypadek narożny, mówi wiele o imperatywnym sposobie myślenia za popularnymi językami.

EDYCJA : Po przemyśleniu komentarza Greenoldmana zrozumiałem, że constnie chodzi bezpośrednio o niezmienność danych; constkoduje w typie metody, czy ma ona skutki uboczne dla instancji.

Możliwe jest użycie mutacji, aby osiągnąć referencyjnie przejrzyste zachowanie. Załóżmy, że masz funkcję, która wywoływana sukcesywnie zwraca różne wartości - na przykład funkcję, która odczytuje pojedynczy znak stdin. Możemy użyć pamięci podręcznej / zapamiętać wyniki tej funkcji, aby uzyskać referencyjnie przejrzysty strumień wartości. Strumień byłby połączoną listą, której węzły wywołują funkcję przy pierwszej próbie odzyskania ich wartości, ale następnie buforują wynik. Więc jeśli stdinconstains Hello, world!, po raz pierwszy spróbować pobrać wartość pierwszego węzła, to będzie czytać jedną chari powrotu H. Następnie będzie nadal wracał Hbez dalszych wezwań do przeczytania char. Podobnie drugi węzeł odczytałby charzstdinprzy pierwszej próbie odzyskania jego wartości, tym razem zwracamy ei buforujemy ten wynik.

Interesującą rzeczą jest to, że zamieniłeś proces z natury stanowy w obiekt, który na pozór jest bezpaństwowy. Aby to osiągnąć, konieczna była jednak mutacja stanu wewnętrznego obiektu (poprzez buforowanie wyników) - mutacja była łagodnym efektem . Nie da się uczynić naszego, CharStream constnawet jeśli strumień zachowuje się jak niezmienna wartość. Teraz wyobraź sobie, że istnieje Streaminterfejs z constmetodami i oczekują tego wszystkie twoje funkcje const Streams. Twój CharStreamnie może implementować interfejs!

( EDYCJA 2: Najwyraźniej istnieje słowo kluczowe C ++ mutable, które pozwala nam oszukiwać i tworzyćCharStream const . Jednak ta luka niszczy constgwarancje - teraz naprawdę nie możesz być pewien, że coś nie zmutuje za pomocą swoich constmetod. Przypuszczam, że to nie tak źle, ponieważ musisz wyraźnie poprosić o lukę, ale nadal całkowicie polegasz na systemie honoru).

Po drugie, załóżmy, że masz funkcje wyższego rzędu - możesz przekazać funkcje jako argumenty do innych funkcji. constness jest częścią sygnatury funkcji, więc nie można przekazywać constniefunkcji jako argumentów funkcji, które oczekują constfunkcji. Ślepe egzekwowanie consttutaj doprowadziłoby do utraty ogólności.

Wreszcie, manipulowanie constobiektem nie gwarantuje, że nie mutuje on jakiegoś zewnętrznego (statycznego lub globalnego) stanu za twoimi plecami, więc constgwarancje nie są tak silne, jak się początkowo wydają.

Nie jest dla mnie jasne, czy kodowanie obecności lub braku efektów ubocznych w systemie typów jest ogólnie dobrą rzeczą.

Doval
źródło
1
+1 dziękuję. Niezmienne obiekty to coś innego niż const C ++ (jest to raczej widok). Ale powiedzmy, C ++ miałby domyślnie const, a varna żądanie pozostawiłby tylko jeden problem - głębokość?
greenoldman
1
@greenoldman Dobry punkt. Minęło trochę czasu, odkąd użyłem C ++, więc zapomniałem, że możesz mieć constodniesienie do nie- constobiektu. Mimo to nie sądzę, aby płytka istniała jakikolwiek sens const. Chodzi o constto, że nie będziesz mógł modyfikować obiektu za pomocą tego odwołania. Nie wolno ci obchodzić const, tworząc brak constodniesienia, więc dlaczego miałoby być obchodzone constprzez mutowanie elementów obiektu?
Doval
+1 :-) Bardzo interesujące problemy w twojej aktualizacji (życie z lambdami non / const i być może słabszy problem ze stanem zewnętrznym), muszę to przetrawić dłużej. W każdym razie, jeśli chodzi o twój komentarz, nie mam nic złego na myśli - wręcz przeciwnie, szukam funkcji, aby zwiększyć przejrzystość kodu (inny: wskaźniki inne niż null). Ta sama kategoria tutaj (przynajmniej jest to mój cel) - definiuję func foo(const String &s)i to jest sygnał, sktóry nie zostanie zmieniony foo.
greenoldman
1
@greenoldman Zdecydowanie widzę apelację i uzasadnienie. Nie sądzę jednak, aby istniało realne rozwiązanie problemu poza unikaniem w jak największym stopniu stanu zmiennego (wraz z innymi nieprzyjemnymi rzeczami nulli dziedziczeniem).
Doval
8

Głównym problemem jest to, że programiści zwykle nie używają go wystarczająco, więc kiedy trafią w miejsce, w którym jest to wymagane, lub opiekun później chce naprawić stałą poprawność, występuje ogromny efekt tętnienia. Nadal możesz pisać doskonale niezmienny kod const, kompilator po prostu go nie wymusi i trudniej będzie go zoptymalizować. Niektóre osoby wolą, aby ich kompilator nie pomagał. Nie rozumiem tych ludzi, ale jest ich wielu.

W językach funkcjonalnych prawie wszystko jest const, a bycie zmiennym jest rzadkim wyjątkiem, jeśli w ogóle jest dozwolone. Ma to kilka zalet, takich jak łatwiejsza współbieżność i łatwiejsze rozumowanie na temat stanu współdzielonego, ale wymaga to przyzwyczajenia się, jeśli pochodzisz z języka o zmiennej kulturze. Innymi słowy, brak chęci constjest sprawą ludzi, a nie kwestią techniczną.

Karl Bielefeldt
źródło
+1 dziękuję. Podczas gdy niezmienny obiekt jest czymś innym niż szukam (możesz sprawić, że obiekt będzie niezmienny w C # lub Javie), nawet przy niezmiennym podejściu musisz zdecydować, czy jest głęboki czy płytki, weź na przykład pojemnik wskaźników / referencji (czy możesz zmień wartość wskazywanych obiektów). Wydaje się, że po dniu głównym problemem jest to, co jest ustawione jako domyślne i można go łatwo rozwiązać przy projektowaniu nowego języka.
greenoldman
1
+1 dla lolz @ „Niektórzy ludzie wolą, aby ich kompilator nie pomagał. Nie rozumiem tych ludzi, ale jest ich dużo”.
Thomas Eding,
6

Wady widzę jako:

  • „zatrucie const” jest problemem, gdy jedna deklaracja const może zmusić poprzednią lub następną deklarację do użycia const, a to może spowodować, że jej poprzednie kolejne deklaracje użyją const, itp. I jeśli zatrucie const wpłynie do klasy w bibliotece że nie masz kontroli, możesz utknąć w złym miejscu.

  • to nie jest absolutna gwarancja. Zwykle zdarzają się przypadki, gdy const chroni tylko odwołanie, ale nie jest w stanie ochronić podstawowych wartości z tego czy innego powodu. Nawet w C istnieją przypadki krawędzi, do których const nie może się dostać, co osłabia obietnicę, którą daje const.

  • ogólnie wydaje się, że sentyment branży odchodzi od silnych gwarancji czasu kompilacji, bez względu na to, jak duży komfort mogą one przynieść tobie i ja. Więc po południu spędzam dotykając 66% mojej bazy kodu z powodu zatrucia const, trochę Pythona guy wybrał 10 doskonale wykonalnych, dobrze zaprojektowanych, dobrze przetestowanych, w pełni funkcjonalnych modułów Pythona. I nawet nie rąbał, kiedy to robił.

Być może więc pytanie brzmi: po co wprowadzać potencjalnie kontrowersyjną cechę, o której nawet niektórzy eksperci są ambiwalentni, kiedy próbują Państwo przyswoić nowy, sztuczny język?

EDYCJA: krótka odpowiedź, nowy język ma strome wzgórze, na które można się wspinać w celu adopcji. To projektanci naprawdę muszą się zastanowić, jakie funkcje są potrzebne, aby uzyskać adopcję. Weźmy na przykład Go. Google trochę brutalnie obciął niektóre z najgłębszych bitew religijnych, dokonując pewnych wyborów w stylu Conana-Barbarzyńcy, i myślę, że z tego, co widziałem, ten język jest lepszy.

Obrabować
źródło
2
Twój drugi pocisk jest nieprawidłowy. W C ++ istnieją trzy / cztery sposoby deklarowania odwołania / wskaźnika wrt. constness: int *zmienny wskaźnik na zmienną wartość, int const *zmienny wskaźnik na stałą wartość, int * constconst wskaźnik na zmienną wartość, int const * constconst wskaźnik na stałą wartość.
fresnel
Dziękuję Ci. Zapytałem o naturę, a nie marketing („odejście przemysłu” nie jest naturą podobną do C ++ const), więc takie „powody” uważam za nieistotne (przynajmniej w przypadku tego pytania). O constzatruciu - czy możesz podać przykład? Ponieważ AFAIK jest zaletą, przekazujesz coś consti powinno pozostać const.
greenoldman
1
To nie jest constproblem, ale zmiana typu naprawdę - cokolwiek zrobisz, zawsze będzie problemem. Rozważ podanie RichString, a następnie uświadomienie sobie, że lepiej przekazać String.
greenoldman
4
@RobY: Nie jestem pewien, do kogo / z kim zwracasz się do tych ostatnich i byłych . Jednak: Po tym popołudniu, przechodząc przez kod, powinieneś się nauczyć, że powinieneś refaktoryzować do stałej poprawności oddolnej, a nie odgórnej, tj. Zaczynać w najbardziej wewnętrznych jednostkach i iść dalej. Być może twoje jednostki również nie były wystarczająco izolowane, zamiast tego były ściśle powiązane lub padłeś ofiarą wewnętrznych systemów.
fresnel
7
„Trujące zatrucie” nie jest tak naprawdę problemem - prawdziwym problemem jest to, że C ++ domyślnie nie jest const. Zbyt łatwo jest nie constrobić rzeczy, które powinny być, constponieważ musisz zdecydować się na to zamiast z niego zrezygnować.
Doval
0

Dodanie constdo języka wiąże się z pewnymi problemami filozoficznymi . Jeśli zadeklarujesz constsię klasie, oznacza to, że klasa nie może się zmienić. Ale klasa jest zbiorem zmiennych połączonych z metodami (lub mutatorami). Abstrakcję osiągamy, ukrywając stan klasy za zestawem metod. Jeśli klasa jest zaprojektowana do ukrywania stanu, potrzebujesz mutatorów, aby zmienić ten stan z zewnątrz, a wtedy constjuż nie jest . Można więc zadeklarować consttylko te klasy, które są niezmienne. constWydaje się więc możliwe tylko w scenariuszach, w których klasy (lub typy, które chcesz zadeklarować const) są trywialne ...

Więc w constkażdym razie potrzebujemy ? Nie sądzę. Myślę, że bez problemu możesz się obejść bez constdobrego projektu. Jakich danych potrzebujesz i gdzie, jakie metody są metodami danych do danych i jakich abstrakcji potrzebujesz do I / O, sieci, widoku itp. Następnie naturalnie dzielisz moduły i klasy, które zajmują się stanem i masz metody i niezmienne klasy, które nie potrzebują stanu.

Myślę, że większość problemów powstaje w przypadku dużych, grubych obiektów danych, które mogą się zapisywać, przekształcać i rysować, a następnie chcesz je na przykład przekazać do renderera. Nie można więc skopiować zawartości danych, ponieważ jest to zbyt kosztowne dla dużego obiektu danych. Ale nie chcesz, żeby to się zmieniło, więc potrzebujesz czegoś takiego const, jak myślisz. Pozwól kompilatorowi odkryć swoje wady projektowe. Jeśli jednak podzielisz te obiekty tylko na niezmienny obiekt danych i zaimplementujesz metody w odpowiedzialnym module, możesz przekazać (tylko) wskaźnik i wykonać czynności, które chcesz zrobić z danymi, jednocześnie gwarantując niezmienność.

Wiem, że w C ++ można zadeklarować niektóre metody consti nie zadeklarować innych metod, ponieważ są mutatorami. A potem jakiś czas potrzebujesz bufora podręcznego, a następnie getter mutuje obiekt (ponieważ leniwie się ładuje) i constjuż go nie ma. Ale o to właśnie chodzi w tej abstrakcji, prawda? Nie wiesz, jaki stan ulega mutacji przy każdym połączeniu.

Niszczyciel
źródło
1
„Więc const wydaje się możliwy tylko w scenariuszach, w których klasy (lub typy, które chcesz deklarować const) są trywialne ...” Trwałe struktury danych są niezmienne i nietrywialne. Po prostu nie zobaczysz ich w C ++, ponieważ prawie wszystkie z nich współużytkują swoje dane wewnętrzne między wieloma instancjami, a C ++ nie ma funkcji wyrzucania elementów bezużytecznych, aby działało. „Myślę, że możesz łatwo obejść się bez const, jeśli masz dobry projekt”. Niezmienne wartości znacznie ułatwiają rozumowanie kodu.
Doval
+1 dziękuję. Czy możesz wyjaśnić „zadeklarować const do klasy”? Czy chodziło Ci o ustawienie klasy jako stałej? Jeśli tak, nie pytam o to - const C ++ działa jak widok, możesz zadeklarować const obok obiektu, a nie klasy (lub coś zmienionego w C ++ 11). Jest też problem z następnym akapitem i słowem „łatwo” - jest dobre pytanie o C ++ - const w C #, a ilość pracy, którą wymaga, jest najważniejsza - dla każdego typu, który zamierzasz włożyć const(w sensie C ++ ) musisz zdefiniować interfejs, także dla wszystkich właściwości.
greenoldman
1
Kiedy mówisz o „klasie”, masz na myśli „instancję klasy”, prawda? Myślę, że to ważne rozróżnienie.
sick