Dlaczego istnieje tak wiele klas ciągów w obliczu std :: string?

56

Wydaje mi się, że wiele większych bibliotek C ++ ostatecznie tworzy własny ciąg znaków. W kodzie klienta albo trzeba użyć jednego z biblioteki ( QString, CString, fbstringitd, jestem pewien, że każdy może wymienić tylko kilka) lub zachować konwersji pomiędzy standardowym typu i jednym z zastosowań bibliotecznych (który większość czasu wiąże co najmniej jedna kopia).

A więc, czy jest w tym jakiś błąd lub coś złego std::string(tak jak auto_ptrsemantyka była zła)? Czy zmieniło się to w C ++ 11?

Tamás Szelei
źródło
32
Nazywa się to „zespołem niewymienionym tutaj”.
Cat Plus Plus
10
@CatPlusPlus QString i CString oba poprzedzały std :: string.
Gort the Robot
8
@Cat Plus Plus: Wydaje się, że ten syndrom nie wpływa na klasę Java String.
Giorgio
20
@Giorgio: Programiści Java są zbyt zajęci wymyślaniem obejść niedociągnięć językowych, aby martwić się o klasy ciągów (tak na marginesie, Android wymyślił String).
Cat Plus Plus
9
@Giorgio: Prawdopodobnie dlatego, że sztywno zakodowane wsparcie syntaktyczne Javy java.lang.String(brak przeciążenia operatora itp.) Sprawiłoby, że korzystanie z czegokolwiek innego byłoby utrudnieniem.
Ślimak mechaniczny

Odpowiedzi:

57

Większość tych większych bibliotek C ++ została uruchomiona, zanim std::stringzostała znormalizowana. Inne obejmują dodatkowe funkcje, które zostały znormalizowane późno lub nadal nie są znormalizowane, takie jak obsługa UTF-8 i konwersja między kodowaniami.

Gdyby biblioteki te zostały zaimplementowane dzisiaj, prawdopodobnie wybraliby zapisywanie funkcji i iteratorów działających na std::stringinstancjach.

Ben Voigt
źródło
5
Obsługa UTF-8 jest znormalizowana od C ++ 98. W tak niewygodnym i częściowo wdrożonym sposobie implementacji, że nikt nie wydaje się być w stanie z niego skorzystać
AProgrammer
9
@AProgrammer: z pewnością charjest wystarczająco duży, aby pomieścić dowolny punkt kodowy UTF-8. AFAIK, to jedyne „wsparcie”, które zapewnia C ++ 98.
Ben Voigt,
4
@AProgrammer: To wsparcie jest naprawdę bezużyteczne.
DeadMG
4
@AProgrammer To ustawienie regionalne jest prawdopodobnie uszkodzone, ponieważ niewchar_t jest wystarczająco duże, aby reprezentować wszystkie punkty kodu Unicode. Co więcej, toczyła się cała dyskusja na temat UTF-16 uważanego za szkodliwy, gdy wysunięto bardzo przekonujący argument, że UTF-8 powinien być używany wyłącznie
Konrad Rudolph
6
@KonradRudolph, nie jest tam uszkodzony system ustawień regionalnych (definicja wchar_t jest „wystarczająco szeroka dla dowolnego obsługiwanego zestawu znaków”); systemy zobowiązujące się do 16 bitów wchar_t jednocześnie zobowiązały się do nieobsługiwania Unicode. Sprawcą jest Unicode, który najpierw gwarantował, że nigdy nie użyje współrzędnych kodowych wymagających więcej niż 16 bitów, a następnie systemów zatwierdzających do 16 bitów wchar_t, a następnie przełączania Unicode potrzebujących więcej niż 16 bitów.
AProgrammer
39

String jest dużym zawstydzeniem dla C ++.

Przez pierwsze 15 lat w ogóle nie zapewniasz klasy ciągów - zmuszając każdy kompilator na każdej platformie i każdego użytkownika do tworzenia własnych.

Następnie tworzysz coś, co jest mylące, czy ma to być pełny interfejs API do manipulacji ciągami, czy tylko kontener znaków STL, z niektórymi algorytmami, które duplikują te na std :: Vector lub są różne.

W przypadku gdy oczywista operacja na łańcuchu, taka jak replace () lub mid (), wiąże się z takim bałaganem iteratorów, że musisz wprowadzić nowe słowo kluczowe „auto”, aby zachować dopasowanie instrukcji na jednej stronie i doprowadzić większość ludzi do rezygnacji z całego języka .

A potem masz unicode 'support' i std :: wstring, które jest po prostu arghh .....

<rant off> dziękuję - czuję się teraz znacznie lepiej.

Martin Beckett
źródło
12
@DeadMG - tak i został znormalizowany w 1998 roku, 15 lat po wynalezieniu, a 6 lat po użyciu nawet MSFT. Tak, iteratory to przydatny sposób na to, aby tablica i lista wyglądały tak samo, czy uważasz, że są oczywistym sposobem na manipulację łańcuchem?
Martin Beckett
3
C with Classes zostało wynalezione w 1983 roku. Nie C ++. Jedynymi bibliotekami Standardowymi są biblioteki określone przez Standard - co, o dziwo, może się zdarzyć tylko wtedy, gdy masz Standard, więc najwcześniejszą możliwą datą dla dowolnej biblioteki Standard jest rok 1998. I iteratory można uznać za dokładnie równe indeksom, ale silnie wpisane. Popieram fakt, że iteratory są do kitu w porównaniu do zakresów, ale to nie jest tak naprawdę specyficzne std::string. Brak klasy String w 1983 r. Nie usprawiedliwia posiadania ich więcej.
DeadMG
8
Myślałem, że iostreams były dużym zakłopotaniem C ++ ...
Doug T.
18
@DeadMG Ludzie używali czegoś o nazwie „C ++” przez wiele lat przed 1998 rokiem. Pierwszy program napisałem używając czegoś o nazwie „C ++” w 1985 roku. Jeśli chcesz powiedzieć, że to nie jest „prawdziwe” C ++, to dobrze, ale wcześniej pisaliśmy kod i musieliśmy skądś pobrać klasę znaków. Kiedy już mieliśmy te starsze podstawy kodu, nie mogliśmy ich dokładnie wyrzucić ani przepisać od zera, kiedy otrzymamy standard. Teraz powinno się zdarzyć, że powinna istnieć klasa strun, która jest dostarczana z frontem.
Gort the Robot
8
@DeadMG - Jeśli nikt nie użyje języka, dopóki nie będzie miał certyfikatu ISO, żaden język nigdy nie zostanie użyty, ponieważ nigdy nie uzyska ISO. Nie ma standardu ISO dla asemblera x86, ale cieszę się, że mogę korzystać z platformy
Martin Beckett
32

Właściwie ... jest kilka problemów std::stringi tak, w C ++ 11 jest trochę lepiej, ale nie wyprzedzajmy siebie.

QStringi CStringsą częścią starych bibliotek, dlatego istniały przed standaryzacją C ++ (podobnie jak SGI STL). Są więc musiał utworzyć klasę.

fbstringrozwiązać bardzo konkretne problemy dotyczące wydajności. Norma określa interfejs, a złożoność algorytmiczna gwarantuje minima, jednak szczegóły dotyczące jakości implementacji są takie, czy ostatecznie są szybkie, czy nie. fbstringma określone optymalizacje ( findna przykład związane z pamięcią masową lub szybsze ).

Inne obawy, które nie zostały tutaj przywołane (en vrac):

  • w C ++ 03 pamięć nie musi być ciągła, co potencjalnie utrudnia interoperacyjność z C. C ++ 11 to rozwiązuje.
  • std::string koduje nieświadomy i nie ma specjalnego kodu dla UTF-8, łatwo jest przechowywać w nim ciąg UTF-8 i nieumyślnie go uszkodzić
  • std::stringinterfejs jest rozdęty , wiele metod można było zaimplementować jako funkcje bezpłatne, a wiele z nich jest powielanych w celu dostosowania zarówno do interfejsu opartego na indeksie, jak i interfejsu opartego na iteratorze.
Matthieu M.
źródło
5
Dotyczy dotyczy # 1 - C ++ 03 21.3.6 / 1 gwarantuje, że c_str()wskaźnik zwróci wskaźnik do ciągłej pamięci, co zapewnia pewną współdziałanie C. Nie można jednak modyfikować danych wskazanych. Typowe obejścia obejmują użycie vector<char>.
John Dibling,
@JohnDibling: Tak, i jest jeszcze inne ograniczenie: może ponieść kopię w nowo przydzielonym miejscu do przechowywania (standard nie mówi, że nie będzie). Oczywiście C ++ 11 również nie uniemożliwia kopiowania, ale ponieważ można to po prostu zrobić &s[0], nie ma już znaczenia :)
Matthieu M.
1
@ MatthieuM .: Wskaźnik uzyskany przez &s[0]nie może wskazywać na łańcuch zakończony znakiem NUL (chyba c_str()że został wywołany od ostatniej modyfikacji).
Ben Voigt,
2
@ Matthieu: Inny bufor nie jest dozwolony. „ c_str()Zwraca: Wskaźnik ptaki, że p + i == &operator[](i)dla każdego iw [0,size()]”.
Ben Voigt
3
Warto również zauważyć, że nikt przy zdrowych zmysłach nie używa już MFC, więc trudno argumentować, że CString to klasa ciągów we współczesnym C ++.
DeadMG
7

Oprócz wymienionych tutaj powodów istnieje jeszcze jedna - binarna kompatybilność . Autorzy bibliotek nie mają kontroli nad tym, jakiej std::stringimplementacji używasz i czy ma taki sam układ pamięci jak ich.

std::stringjest szablonem, więc jego implementacja pochodzi z lokalnych nagłówków STL. Teraz wyobraź sobie, że używasz lokalnej wersji STL o zoptymalizowanej wydajności, w pełni zgodnej ze standardem. Na przykład, możesz zdecydować się na włożenie bufora statycznego std::stringdo każdego z nich, aby zmniejszyć liczbę alokacji dynamicznych i braków w pamięci podręcznej. W rezultacie układ pamięci i / lub rozmiar implementacji jest inny niż w bibliotece.

Jeśli tylko układ jest inny, niektóre std::stringwywołania funkcji składowych instancji przekazywane z biblioteki do klienta lub na odwrót mogą się nie powieść, w zależności od tego, które elementy zostały przeniesione.

Jeśli rozmiar jest również inny, wszystkie typy bibliotek posiadające std::stringelement członkowski będą miały różny rozmiar po sprawdzeniu w bibliotece i kodzie klienta. Członkowie danych następujący po std::stringelemencie będą również przesunięciami przesunięcia, a każdy bezpośredni dostęp / dostęp wbudowany od klienta zwróci śmieci, pomimo „wyglądania OK” podczas debugowania samej biblioteki.

Konkluzja - jeśli biblioteka i kod klienta zostaną skompilowane w różnych std::stringwersjach, będą łączyły się dobrze, ale może to spowodować pewne nieprzyjemne, trudne do zrozumienia błędy. Jeśli zmienisz std::stringimplementację, wszystkie biblioteki ujawniające członków z STL muszą zostać ponownie skompilowane, aby pasowały do std::stringukładu klienta . A ponieważ programiści chcą, aby ich biblioteki były niezawodne, rzadko można je zobaczyć w std::stringdowolnym miejscu.

Szczerze mówiąc, dotyczy to wszystkich typów STL. IIRC nie mają znormalizowanego układu pamięci.

gwiazdorrr
źródło
2
Musisz być programistą * nix. Kompatybilność binarna C ++ nie jest równa na wszystkich platformach, a szczególnie w systemie Windows klasy NO zawierające elementy danych są przenośne między kompilatorami.
Ben Voigt
(Mam na myśli oprócz typów POD, a nawet wtedy potrzebne są wyraźne wymagania dotyczące pakowania)
Ben Voigt
1
Dzięki za wkład, chociaż nie mówię o innym kompilatorze, mówię o innym STL.
gwiazdorrr
1
+1: ABI to ogromny powód, aby rzucić własną wersję klasy dostarczonej przez kompilator. Tylko w tym przypadku chciałbym, aby była to zaakceptowana odpowiedź.
Thomas Eding
6

Istnieje wiele odpowiedzi na pytanie, ale oto kilka:

  1. Dziedzictwo. Wiele bibliotek i klas ciągów zostało napisanych PRZED istnieniem std :: string.

  2. Dla zgodności z kodem w C. Biblioteką std :: string jest C ++, ponieważ istnieją inne biblioteki łańcuchów, które działają z C i C ++.

  3. Aby uniknąć alokacji dynamicznych. Biblioteka std :: string korzysta z alokacji dynamicznej i może nie być odpowiednia dla systemów osadzonych, kodu przerwań lub kodu w czasie rzeczywistym lub do funkcji niskiego poziomu.

  4. Szablony Biblioteka std :: string oparta jest na szablonach. Do niedawna wiele kompilatorów C ++ miało słabo działającą lub nawet błędną obsługę szablonów. Niestety pracuję w branży, która korzysta z wielu niestandardowych narzędzi, a jeden z naszych łańcuchów narzędzi od dużego gracza w branży nie „oficjalnie” w 100% obsługuje C ++ (z błędami są szablony i in.).

Prawdopodobnie jest też wiele innych ważnych powodów.

Adisak
źródło
2
„Dość niedawno” oznacza „Minęła dekada, odkąd nawet Visual Studio miało dla nich całkiem rozsądne wsparcie”?
DeadMG
@DeadMG - Visual Studio nie jest jedynym niezgodnym kompilatorem na świecie. Pracuję w grach wideo i często pracujemy nad niestandardowymi kompilatorami dla niewydanych platform sprzętowych (dzieje się to co kilka lat w cyklach konsoli lub gdy pojawia się nowy sprzęt). „Dość niedawno” oznacza dziś - obecnie niektóre kompilatory nie obsługują dobrze szablonów. Nie mogę się sprecyzować, nie naruszając NDA, ale obecnie pracuję na platformie z niestandardowymi łańcuchami narzędzi, w których obsługa C ++ - szczególnie zgodność z szablonami - jest uważana za „eksperymentalną”.
Adisak
4

Chodzi głównie o Unicode. Standardowa obsługa Unicode jest co najwyżej fatalna i każdy ma własne potrzeby w zakresie Unicode. Na przykład, ICU obsługuje wszystkie funkcje Unicode, jakie kiedykolwiek chciałeś, za najbardziej obrzydliwym interfejsem generowanym automatycznie z Java, jaki możesz sobie wyobrazić, a jeśli jesteś na Uniksie, że utkniesz z UTF-16, może nie być twoim pomysłem miło spędzony czas.

Ponadto wiele osób potrzebuje różnych poziomów obsługi Unicode - nie wszyscy potrzebują złożonych interfejsów API układu tekstu i tym podobnych rzeczy. Łatwo więc zrozumieć, dlaczego istnieje wiele klas ciągów - standardowa jest dość do kitu i wszyscy mają inne potrzeby niż nowe, nikt nie jest w stanie stworzyć jednej klasy, która może wykonywać wiele różnych platform obsługujących Unicode z przyjemnym interfejsem.

Moim zdaniem jest to głównie wina Komitetu C ++ za niepoprawne wsparcie dla Unicode - w 1998 lub 2003 roku, być może było to zrozumiałe, ale nie w C ++ 11. Mam nadzieję, że w C ++ 17 poradzą sobie lepiej.

DeadMG
źródło
Witaj, C ++ 20 tutaj, zgadnij, co się stało z obsługą Unicode?
Passer Do
-4

Jest tak, ponieważ każdy programista ma coś do udowodnienia i odczuwa potrzebę stworzenia własnej niesamowitej, szybszej klasy ciągów dla swojej jednej, niesamowitej funkcji. Zazwyczaj jest to trochę zbyteczne i prowadzi do wszelkiego rodzaju dodatkowych konwersji ciągów z mojego doświadczenia.

Chad Stewart
źródło
7
Czy to prawda, spodziewałbym się zobaczyć podobną liczbę implementacji String w językach takich jak Java, gdzie przez cały czas dostępna była dobra implementacja.
Bill K
@BillK ciąg Java jest ostateczny, więc musisz umieścić nową funkcjonalność w innym miejscu.
I chodzi mi o to, że nawet będąc ostatecznym, przez 20 lat nigdy nie widziałem, żeby ktoś pisał niestandardowe ulepszenie łańcucha (Cóż, próbowałem poprawić wydajność konkatenacji łańcucha, ale okazuje się, że java jest o wiele mądrzejszy w łańcuch + łańcuch niż ty ” d wyobraź sobie)
Bill K
2
@ Bill: Może to mieć związek z inną kulturą. C ++ przyciąga tych, którzy chcą zrozumieć szczegóły niskiego poziomu. Java przyciąga tych, którzy chcą po prostu wykonać zadanie, korzystając z czyichś elementów. (Uwaga: nie jest to stwierdzenie o konkretnej osobie decydującej się na użycie jednego z tych języków, ale o celach projektowych i kulturze tych języków)
Ben Voigt