Kto zaprojektował / zaprojektował IOStreams w C ++ i czy nadal będzie uważany za dobrze zaprojektowany według dzisiejszych standardów? [Zamknięte]

128

Po pierwsze, może się wydawać, że proszę o subiektywne opinie, ale nie o to mi chodzi. Bardzo chciałbym usłyszeć kilka uzasadnionych argumentów na ten temat.


Mając nadzieję na trochę wglądu w to, jak powinny być projektowane nowoczesne ramy strumieni / serializacji, niedawno kupiłem książkę Standard C ++ IOStreams and Locales autorstwa Angeliki Langer i Klausa Krefta . Pomyślałem, że gdyby IOStreams nie był dobrze zaprojektowany, to w pierwszej kolejności nie trafiłby do standardowej biblioteki C ++.

Po przeczytaniu różnych części tej książki zaczynam wątpić, czy IOStreams można porównać np. Z STL z ogólnego architektonicznego punktu widzenia. Przeczytaj na przykład ten wywiad z Alexandrem Stepanovem („wynalazcą” STL), aby dowiedzieć się o niektórych decyzjach projektowych, które zostały podjęte w ramach STL.

Co mnie szczególnie zaskakuje :

  • Wydaje się, że nie wiadomo, kto był odpowiedzialny za ogólny projekt IOStreams (chciałbym przeczytać kilka podstawowych informacji na ten temat - czy ktoś zna dobre zasoby?);

  • Gdy zagłębisz się w bezpośrednią powierzchnię IOStreams, np. Jeśli chcesz rozszerzyć IOStreams o własne klasy, otrzymasz interfejs z dość tajemniczymi i mylącymi nazwami funkcji składowych, np. getloc/ imbue, uflow/ underflow, snextc/ sbumpc/ sgetc/ sgetn, pbase/ pptr/ epptr(I jest prawdopodobnie jeszcze gorsze przykłady). To sprawia, że ​​znacznie trudniej jest zrozumieć ogólny projekt i sposób, w jaki poszczególne części współpracują. Nawet książka, o której wspomniałem powyżej, nie pomaga tak bardzo (IMHO).


Stąd moje pytanie:

Gdybyś musiał oceniać na podstawie dzisiejszych standardów inżynierii oprogramowania (jeśli faktycznie istnieje jakakolwiek ogólna zgoda co do nich), czy IOStreams w C ++ nadal byłoby uważane za dobrze zaprojektowane? (Nie chciałbym podnosić swoich umiejętności projektowania oprogramowania z czegoś, co jest ogólnie uważane za przestarzałe).

stakx - już nie wnoszący wkładu
źródło
7
Ciekawa opinia Herba Suttera stackoverflow.com/questions/2485963/… :) Szkoda, że ​​facet wyszedł z SO po zaledwie kilku dniach uczestnictwa
Johannes Schaub - litb
5
Czy jest jeszcze ktoś, kto widzi mieszanie się problemów w strumieniach STL? Strumień jest zwykle zaprojektowany do czytania lub zapisywania bajtów i nic więcej. Rzeczą, która może odczytywać lub zapisywać określone typy danych, jest program formatujący (który może, ale nie musi, używać strumienia do odczytu / zapisu sformatowanych bajtów). Mieszanie obu w jedną klasę sprawia, że ​​implementowanie własnych strumieni jest jeszcze bardziej skomplikowane.
mmmmmmmm
4
@rsteven, istnieje oddzielenie tych obaw. std::streambufjest klasą bazową do odczytu i zapisu bajtów, a istream/ ostreamsłuży do formatowania wejścia i wyjścia, przyjmując wskaźnik do std::streambufjako miejsce docelowe / źródło.
Johannes Schaub - litb
1
@litb: Ale czy możliwe jest przełączenie strumienia używanego przez strumień (program formatujący)? Więc może chcę użyć formatowania STL, ale chcę zapisać dane za pośrednictwem określonego streambuf?
mmmmmmmm
2
@rstevens,ostream foo(&somebuffer); foo << "huh"; foo.rdbuf(cout.rdbuf()); foo << "see me!";
Johannes Schaub - litb

Odpowiedzi:

31

Kilka nieprzemyślane pomysły trafiły do normy: auto_ptr, vector<bool>, valarrayi export, żeby wymienić tylko kilka. Dlatego nie uważałbym obecności IOStreams za oznakę dobrego projektu.

IOStreams ma burzliwą historię. W rzeczywistości są one przeróbką wcześniejszej biblioteki strumieni, ale zostały stworzone w czasie, gdy wiele dzisiejszych idiomów C ++ nie istniało, więc projektanci nie mieli korzyści z perspektywy czasu. Jedną z kwestii, która stała się oczywista z czasem, było to, że prawie niemożliwe jest wdrożenie IOStreams tak wydajnie, jak stdio C, ze względu na obfite wykorzystanie funkcji wirtualnych i przekazywanie do wewnętrznych obiektów buforowych przy nawet najmniejszej ziarnistości, a także dzięki pewnej niezrozumiałej dziwności. w sposobie definiowania i implementacji ustawień regionalnych. Muszę przyznać, że pamięć o tym jest dość niewyraźna; Pamiętam, że był to temat intensywnej debaty kilka lat temu, moderowana na comp.lang.c ++.

Marcelo Cantos
źródło
3
Dziękuję za wkład. Będę przeglądać comp.lang.c++.moderatedarchiwum i umieszczać linki na dole mojego pytania, jeśli znajdę coś wartościowego. - Poza tym, śmiem się z tobą nie zgodzić co do auto_ptr: Po przeczytaniu Wyjątkowego C ++ Herba Suttera wydaje się, że jest to bardzo przydatna klasa przy implementacji wzorca RAII.
stakx - nie publikuje już
5
@stakx: Niemniej jednak jest on przestarzały i zastępowany przez unique_ptrjaśniejszą i silniejszą semantykę.
UncleBens
3
@UncleBens unique_ptrwymaga odwołania do wartości r. Więc w tym momencie auto_ptrjest bardzo potężny wskaźnik.
Artem
7
Ale auto_ptrma schrzanioną semantykę kopiowania / przypisania, co czyni ją niszą dla błędów dereferencyjnych ...
Matthieu M.
5
@TokenMacGuy: to nie jest wektor i nie przechowuje wartości logicznych. Co czyni to nieco mylącym. ;)
jalf
40

Jeśli chodzi o to, kto je zaprojektował, oryginalna biblioteka została (nie jest zaskoczeniem) stworzona przez Bjarne Stroustrupa, a następnie ponownie zaimplementowana przez Dave Presotto. Zostało to następnie przeprojektowane i ponownie zaimplementowane przez Jerry'ego Schwarza dla Cfront 2.0, wykorzystując ideę manipulatorów Andrew Koeniga. Standardowa wersja biblioteki jest oparta na tej implementacji.

Źródło „Projekt i ewolucja języka C ++”, sekcja 8.3.1.

Quuxplusone
źródło
3
@Neil - nut, jaka jest Twoja opinia o projekcie? Opierając się na Twoich innych odpowiedziach, wiele osób chciałoby usłyszeć Twoją opinię ...
DVK
1
@DVK Właśnie opublikowałem moją opinię jako oddzielną odpowiedź.
2
Właśnie znalazłem transkrypcję wywiadu z Bjarne Stroustrupem, w którym wspomina on o niektórych fragmentach historii IOStreams: www2.research.att.com/~bs/01chinese.html (ten link wydaje się być teraz tymczasowo uszkodzony, ale możesz spróbować Pamięć podręczna stron Google)
stakx - nie publikuje już
2
Zaktualizowany link: stroustrup.com/01chinese.html .
FrankHB
28

Gdybyś musiał oceniać na podstawie dzisiejszych standardów inżynierii oprogramowania (jeśli faktycznie istnieje jakakolwiek ogólna zgoda co do nich), czy IOStreams w C ++ nadal byłoby uważane za dobrze zaprojektowane? (Nie chciałbym podnosić swoich umiejętności projektowania oprogramowania z czegoś, co jest powszechnie uważane za przestarzałe).

Powiedziałbym NIE z kilku powodów:

Słaba obsługa błędów

Warunki błędów należy zgłaszać z wyjątkami, a nie z operator void*.

Anty-wzorzec „obiektu zombie” powoduje takie błędy .

Słaba separacja między formatowaniem a we / wy

To sprawia, że ​​obiekty strumieniowe są niepotrzebnie złożone, ponieważ muszą zawierać dodatkowe informacje o stanie do formatowania, niezależnie od tego, czy jest to potrzebne, czy nie.

Zwiększa również prawdopodobieństwo pisania błędów, takich jak:

using namespace std; // I'm lazy.
cout << hex << setw(8) << setfill('0') << x << endl;
// Oops!  Forgot to set the stream back to decimal mode.

Jeśli zamiast tego napisałeś coś takiego:

cout << pad(to_hex(x), 8, '0') << endl;

Nie byłoby bitów stanu związanych z formatowaniem i nie byłoby problemu.

Zauważ, że we "nowoczesnych" językach, takich jak Java, C # i Python, wszystkie obiekty mają funkcję toString/ ToString/, __str__która jest wywoływana przez procedury we / wy. AFAIK, tylko C ++ robi to na odwrót, używając stringstreamjako standardowego sposobu konwersji na ciąg.

Słabe wsparcie dla i18n

Dane wyjściowe oparte na Iostream dzielą literały ciągów na części.

cout << "My name is " << name << " and I am " << occupation << " from " << hometown << endl;

Ciągi formatujące umieszczają całe zdania w literałach łańcuchowych.

printf("My name is %s and I am %s from %s.\n", name, occupation, hometown);

To drugie podejście jest łatwiejsze do dostosowania do bibliotek internacjonalizacji, takich jak GNU gettext, ponieważ użycie całych zdań zapewnia tłumaczom szerszy kontekst. Jeśli twoja procedura formatowania łańcuchów obsługuje zmianę kolejności (jak $parametry POSIX printf), to również lepiej radzi sobie z różnicami w kolejności słów między językami.

dan04
źródło
4
W rzeczywistości dla i18n zamiany powinny być identyfikowane przez pozycje (% 1,% 2, ..), ponieważ translacja może wymagać zmiany kolejności parametrów. W przeciwnym razie w pełni się zgadzam - +1.
peterchen
4
@peterchen: To co POSIX $Specyfikatory na printfto.
jamesdlin,
2
Problemem nie są ciągi formatujące, tylko to, że C ++ ma zmienne varargs, które nie są bezpieczne dla typów.
dan04
5
Od C ++ 11 ma teraz zmienne varargs bezpieczne dla typów.
Mooing Duck,
2
IMHO „dodatkowe informacje stanowe” to najgorszy problem. cout jest firmą globalną; dołączenie do niego flag formatowania powoduje, że flagi te stają się globalne, a jeśli weźmiesz pod uwagę, że większość z nich ma zamierzony zasięg kilku linii, to jest to dość okropne. Można by to naprawić za pomocą klasy „formatera”, która wiąże się z ostreamem, ale zachowuje swój własny stan. I rzeczy zrobione z cout ogólnie wyglądają okropnie w porównaniu do tego samego, co zrobione z printf (kiedy to jest możliwe) ..
greggo
17

Publikuję to jako osobną odpowiedź, ponieważ jest to czysta opinia.

Wykonywanie operacji wejścia i wyjścia (szczególnie wejścia) jest bardzo, bardzo trudnym problemem, więc nie jest zaskakujące, że biblioteka iostreams jest pełna elementów i rzeczy, które z perspektywy czasu można było zrobić lepiej. Ale wydaje mi się, że wszystkie biblioteki I / O, w jakimkolwiek języku są takie. Nigdy nie używałem języka programowania, w którym system I / O byłby piękną rzeczą, która sprawiała, że ​​podziwiałem jego projektanta. Biblioteka iostreams ma zalety, szczególnie w porównaniu z biblioteką CI / O (rozszerzalność, bezpieczeństwo typów itp.), Ale nie sądzę, aby ktokolwiek traktował ją jako przykład świetnego OO lub ogólnego projektu.


źródło
16

Moja opinia o C ++ iostreams znacznie się poprawiła w czasie, szczególnie po tym, jak zacząłem je rozszerzać, implementując własne klasy strumieniowe. Zacząłem doceniać rozszerzalność i ogólny projekt, pomimo śmiesznie kiepskich nazw funkcji składowych, takich jak xsputnczy cokolwiek innego. Niezależnie od tego, myślę, że strumienie I / O są ogromnym ulepszeniem w stosunku do C stdio.h, które nie ma bezpieczeństwa typów i jest pełne poważnych luk w zabezpieczeniach.

Myślę, że głównym problemem ze strumieniami we / wy jest to, że łączą one dwie powiązane, ale nieco ortogonalne koncepcje: formatowanie tekstu i serializację. Z jednej strony strumienie we / wy są przeznaczone do tworzenia czytelnej dla człowieka, sformatowanej tekstowej reprezentacji obiektu, az drugiej do serializacji obiektu do formatu przenośnego. Czasami te dwa cele są takie same, ale innym razem prowadzi to do poważnych irytujących niekongruencji. Na przykład:

std::stringstream ss;
std::string output_string = "Hello world";
ss << output_string;

...

std::string input_string;
ss >> input_string;
std::cout << input_string;

Tutaj to, co otrzymujemy jako dane wejściowe, nie jest tym, co pierwotnie wyprowadziliśmy do strumienia. Dzieje się tak, ponieważ <<operator wyprowadza cały ciąg, podczas gdy >>operator będzie czytać ze strumienia tylko do momentu napotkania znaku odstępu, ponieważ w strumieniu nie ma informacji o długości . Więc nawet jeśli wypisujemy obiekt typu string zawierający "hello world", wprowadzimy tylko obiekt typu string zawierający "hello". Więc chociaż strumień spełnił swoje zadanie jako narzędzie do formatowania, nie udało mu się poprawnie serializować, a następnie odserializować obiektu.

Można powiedzieć, że strumienie we / wy nie zostały zaprojektowane jako urządzenia do serializacji, ale jeśli tak jest, do czego naprawdę służą strumienie wejściowe ? Poza tym w praktyce strumienie I / O są często używane do serializacji obiektów, ponieważ nie ma innych standardowych narzędzi do serializacji. Rozważmy boost::date_timelub boost::numeric::ublas::matrix, gdzie jeśli wyjście obiekt matryca z <<operatorem, dostaniesz dokładnie taki sam matrycę gdy wejście jest za pomocą >>operatora. Ale aby to osiągnąć, projektanci Boost musieli przechowywać informacje o liczbie kolumn i liczbie wierszy jako dane tekstowe na wyjściu, co zagraża rzeczywistemu czytelnemu dla człowieka wyświetlaczowi. Znowu niezręczne połączenie funkcji formatowania tekstu i serializacji.

Zwróć uwagę, jak większość innych języków oddziela te dwie usługi. Na przykład w Javie formatowanie odbywa się za pomocą toString()metody, a serializacja za pośrednictwem Serializableinterfejsu.

Moim zdaniem najlepszym rozwiązaniem byłoby wprowadzenie strumieni opartych na bajtach , obok standardowych strumieni opartych na znakach . Te strumienie działałyby na danych binarnych, bez obawy o formatowanie / wyświetlanie czytelne dla człowieka. Mogą być używane wyłącznie jako narzędzia serializacji / deserializacji, do tłumaczenia obiektów C ++ na przenośne sekwencje bajtów.

Charles Salvia
źródło
dzięki za odpowiedź. Mogę się mylić co do tego, ale jeśli chodzi o twój ostatni punkt (strumienie oparte na bajtach i strumieniach opartych na znakach), czy (częściowa?) Odpowiedź IOStream na to nie jest separacją między buforami strumienia (konwersja znaków, transport i buforowanie) i strumienie (formatowanie / parsowanie)? I czy nie mógłbyś utworzyć nowych klas strumienia, które są przeznaczone wyłącznie do (czytelnej dla komputera) serializacji i deserializacji oraz inne, które są unikalnie dostosowane do (czytelnego dla człowieka) formatowania i analizowania?
stakx - nie publikuje już
@stakx, tak, i faktycznie to zrobiłem. Jest to nieco bardziej irytujące, niż się wydaje, ponieważ std::char_traitsnie można przenosić wyspecjalizowanych plików unsigned char. Istnieją jednak obejścia, więc myślę, że rozszerzalność znów przychodzi na ratunek. Ale myślę, że to, że strumienie oparte na bajtach nie są standardowe, jest słabością biblioteki.
Charles Salvia
4
Ponadto implementowanie strumieni binarnych wymaga zaimplementowania nowych klas strumieni i nowych klas buforów, ponieważ kwestie formatowania nie są całkowicie oddzielone od std::streambuf. W zasadzie jedyną rzeczą, którą rozszerzasz, jest std::basic_iosklasa. Jest więc linia, w której „rozszerzanie” przechodzi na obszar „całkowicie reimplementacji”, a tworzenie strumienia binarnego z urządzeń C ++ we / wy strumienia wydaje się zbliżać do tego punktu.
Charles Salvia
dobrze powiedziane i dokładnie to, co podejrzewałem. A fakt, że zarówno C, jak i C ++ robią wszystko, aby nie dawać gwarancji dotyczących określonych szerokości bitów i reprezentacji, może rzeczywiście stać się problematyczny, jeśli chodzi o wykonywanie operacji we / wy.
stakx - już nie publikuje
serializować obiekt do formatu przenośnego. Nie, nigdy nie mieli tego obsługiwać
ciekawy facet
12

Zawsze uważałem, że C ++ IOStreams jest źle zaprojektowany: ich implementacja bardzo utrudnia prawidłowe zdefiniowanie nowego typu strumienia. łączą również funkcje io i funkcje formatowania (pomyśl o manipulatorach).

Osobiście najlepszy projekt i implementacja strumienia, jaki kiedykolwiek znalazłem, leży w języku programowania Ada. jest wzorem odsprzęgania, radością z tworzenia nowych typów strumieni, a funkcje wyjściowe działają zawsze niezależnie od używanego strumienia. Dzieje się tak dzięki najmniejszemu wspólnemu mianownikowi: wysyłasz bajty do strumienia i to wszystko. funkcje strumieniowe dbają o umieszczenie bajtów w strumieniu, ich zadaniem nie jest np. formatowanie liczby całkowitej na szesnastkową (oczywiście istnieje zestaw atrybutów typu, odpowiednik składowej klasy, zdefiniowany do obsługi formatowania)

Chciałbym, żeby C ++ był tak prosty w odniesieniu do strumieni ...

Adrien Plisson
źródło
Książka, o której wspomniałem, wyjaśnia podstawową architekturę IOStreams w następujący sposób: Jest warstwa transportowa (klasy bufora strumienia) i warstwa parsowania / formatowania (klasy strumieniowe). Te pierwsze są odpowiedzialne za odczytywanie / zapisywanie znaków z / do strumienia bajtowego, podczas gdy drugie są odpowiedzialne za analizowanie znaków lub serializowanie wartości na znaki. Wydaje się to wystarczająco jasne, ale nie jestem pewien, czy te obawy są naprawdę wyraźnie rozdzielone w rzeczywistości, szczególnie. gdy w grę wchodzą lokalizacje. - Zgadzam się też z Tobą co do trudności implementacji nowych klas strumieni.
stakx - nie publikuje już
„mix io ​​funkcje i funkcje formatowania” <- Co w tym złego? W pewnym sensie o to chodzi w bibliotece. Jeśli chodzi o tworzenie nowych strumieni, powinieneś utworzyć streambuf zamiast strumienia i skonstruować zwykły strumień wokół streambuf.
Billy ONeal
wydaje się, że odpowiedzi na to pytanie sprawiły, że zrozumiałem coś, czego nigdy mi nie wyjaśniono: zamiast strumienia powinienem wyprowadzić streambuf ...
Adrien Plisson
@stakx: Jeśli warstwa streambuf zrobiła to, co powiedziałeś, byłoby dobrze. Ale konwersja między sekwencją znaków a bajtem jest pomieszana z rzeczywistym I / O (plik, konsola itp.). Nie ma możliwości wykonania operacji we / wy pliku bez wykonania konwersji znaków, co jest bardzo niefortunne.
Ben Voigt,
9

Myślę, że projekt IOStreams jest genialny pod względem możliwości rozbudowy i użyteczności.

  1. Bufory strumieniowe: spójrz na rozszerzenia boost.iostream: twórz gzip, tee, kopiuj strumienie w kilku wierszach, twórz specjalne filtry i tak dalej. Bez niej nie byłoby to możliwe.
  2. Integracja lokalizacji i integracja formatowania. Zobacz, co można zrobić:

    std::cout << as::spellout << 100 << std::endl;
    

    Można wydrukować: „sto” lub nawet:

    std::cout << translate("Good morning")  << std::endl;
    

    Można wydrukować „Bonjour” lub „בוקר טוב” w zależności od ustawień regionalnych std::cout!

    Takie rzeczy można zrobić tylko dlatego, że iostreamy są bardzo elastyczne.

Czy można to zrobić lepiej?

Oczywiście, że tak! W rzeczywistości jest wiele rzeczy, które można by poprawić ...

Dziś jest to dość bolesne, aby wyprowadzić z stream_bufferniego poprawnie , dodanie dodatkowych informacji o formatowaniu do strumienia jest dość nietrywialne, ale możliwe.

Ale patrząc wstecz wiele lat temu, nadal projekt biblioteki był na tyle dobry, że mógł przynieść wiele gadżetów.

Ponieważ nie zawsze możesz zobaczyć pełny obraz, ale jeśli zostawisz punkty za rozszerzenia, daje to znacznie lepsze możliwości, nawet w punktach, o których nie myślałeś.

Artem
źródło
5
Czy możesz dodać komentarz, dlaczego twoje przykłady dla punktu 2 byłyby lepsze niż zwykłe użycie czegoś takiego jak print (spellout(100));i print (translate("Good morning"));Wydawałoby się to dobrym pomysłem, ponieważ oddziela formatowanie i i18n od I / O.
Harmonogram
3
Ponieważ można to przetłumaczyć zgodnie z językiem nasyconym w strumieniu. tj .: french_output << translate("Good morning"); english_output << translate("Good morning") dałby ci: „Bonjour dzień dobry”
Artem
3
Lokalizacja jest znacznie trudniejsza, gdy trzeba napisać '<< "tekst" << wartość' w jednym języku, ale '<< wartość << "tekst"' w innym - w porównaniu z printf
Martin Beckett
@Martin Beckett Wiem, zajrzyj do biblioteki Boost.Locale, co się dzieje, że w takim przypadku to robisz out << format("text {1}") % valuei można to przetłumaczyć "{1} translated". Więc to działa dobrze ;-).
Artem
16
To, co „można zrobić”, nie jest bardzo istotne. Jesteś programistą, wszystko można zrobić przy odpowiednim wysiłku. Ale IOStreams sprawia, że ​​osiągnięcie większości tego, co można zrobić, jest strasznie bolesne . Zwykle za kłopoty uzyskujesz kiepską wydajność.
jalf
2

(Ta odpowiedź jest oparta na mojej opinii)

Myślę, że IOStreamy są znacznie bardziej złożone niż ich odpowiedniki funkcyjne. Kiedy piszę w C ++, nadal używam nagłówków cstdio dla "starego stylu" I / O, które uważam za dużo bardziej przewidywalne. Na marginesie (chociaż nie jest to naprawdę ważne; bezwzględna różnica czasu jest znikoma) wielokrotnie udowodniono, że IOStreams są wolniejsze niż CI / O.

Delan Azabani
źródło
Myślę, że masz na myśli raczej „funkcję” niż „funkcjonalność”. programowanie funkcjonalne tworzy kod, który wygląda jeszcze gorzej niż programowanie ogólne.
Chris Becke
Dziękuję za wskazanie tego błędu; Zredagowałem odpowiedź, aby odzwierciedlić poprawkę.
Delan Azabani
5
IOStreams prawie na pewno musiałby być wolniejszy niż klasyczne stdio; Gdybym otrzymał zadanie zaprojektowania rozszerzalnej i łatwej w użyciu struktury strumieni we / wy, prawdopodobnie oceniłbym prędkość jako drugorzędną, biorąc pod uwagę, że prawdziwymi wąskimi gardłami będą prawdopodobnie prędkość we / wy plików lub przepustowość ruchu sieciowego.
stakx - nie publikuje już
1
Zgadzam się, że dla I / O lub sieci prędkość obliczeniowa nie ma większego znaczenia. Pamiętaj jednak, że C ++ do konwersji liczb / ciągów używa sstringstream. Myślę, że prędkość ma znaczenie, chociaż jest drugorzędna.
Matthieu M.
1
@stakx file We / Wy i wąskie gardła sieciowe są funkcją kosztów „na bajt”, które są dość małe i drastycznie obniżone przez ulepszenia technologii. Ponadto, biorąc pod uwagę DMA, te narzuty nie zabierają czasu procesora innym wątkom na tej samej maszynie. Tak więc, jeśli robisz sformatowane dane wyjściowe, koszt zrobienia tego wydajnie lub nie, może z łatwością być znaczny (przynajmniej nie przesłaniany przez dysk lub sieć; bardziej prawdopodobne jest, że jest on przyćmiony przez inne przetwarzanie w aplikacji).
greggo
2

Korzystając z IOStream zawsze napotykam niespodzianki.

Biblioteka wydaje się być zorientowana na tekst, a nie binarnie. To może być pierwsza niespodzianka: użycie flagi binarnej w strumieniach plików nie wystarczy do uzyskania zachowania binarnego. Użytkownik Charles Salvia powyżej zaobserwował to poprawnie: IOStreams miesza aspekty formatowania (gdzie chcesz uzyskać ładny wynik, np. Ograniczone cyfry dla liczb zmiennoprzecinkowych) z aspektami serializacji (gdzie nie chcesz utraty informacji). Prawdopodobnie dobrze byłoby oddzielić te aspekty. Boost.Serialization robi to za połowę. Masz funkcję serializacji, która kieruje do elementów wstawiających i wyodrębniających, jeśli chcesz. Już masz napięcie między oboma aspektami.

Wiele funkcji ma również mylącą semantykę (np. Get, getline, ignore and read. Niektóre wyodrębniają ogranicznik, inne nie; także niektóre set eof). Dalej niektórzy wspominają o dziwnych nazwach funkcji podczas implementowania strumienia (np. Xsputn, uflow, underflow). Sytuacja się pogarsza, gdy używa się wariantów wchar_t. Wifstream wykonuje tłumaczenie na wielobajtowe, podczas gdy wstringstream nie. Binarne I / O nie działa po wyjęciu z pudełka z wchar_t: masz nadpisać codecvt.

Buforowane we / wy c (tj. PLIK) nie jest tak potężne jak jego odpowiednik w C ++, ale jest bardziej przejrzyste i ma znacznie mniej intuicyjne zachowanie.

Jednak za każdym razem, gdy napotykam IOStream, przyciąga mnie jak ćma do ognia. Prawdopodobnie byłoby dobrze, gdyby jakiś naprawdę sprytny facet dobrze przyjrzał się całej architekturze.

gast128
źródło
1

Nie mogę nie odpowiedzieć na pierwszą część pytania (kto to zrobił?). Ale odpowiedziano na to w innych postach.

Jeśli chodzi o drugą część pytania (dobrze zaprojektowana?), Moja odpowiedź brzmi: „Nie!”. Oto mały przykład, który od lat sprawia, że ​​kręcę głową z niedowierzaniem:

#include <stdint.h>
#include <iostream>
#include <vector>

// A small attempt in generic programming ;)
template <class _T>
void ShowVector( const char *title, const std::vector<_T> &v)
{
    std::vector<_T>::const_iterator iter;
    std::cout << title << " (" << v.size() << " elements): ";
    for( iter = v.begin(); iter != v.end(); ++iter )
    {
        std::cout << (*iter) << " ";
    }
    std::cout << std::endl;
}
int main( int argc, const char * argv[] )
{
    std::vector<uint8_t> byteVector;
    std::vector<uint16_t> wordVector;
    byteVector.push_back( 42 );
    wordVector.push_back( 42 );
    ShowVector( "Garbled bytes as characters output o.O", byteVector );
    ShowVector( "With words, the numbers show as numbers.", wordVector );
    return 0;
}

Powyższy kod generuje bzdury z powodu projektu iostream. Z pewnych powodów, których nie rozumiem, traktują bajty uint8_t jako znaki, podczas gdy większe typy całkowite są traktowane jak liczby. Qed Zły projekt.

Nie mogę też wymyślić sposobu, aby to naprawić. Typ może równie dobrze być zmiennoprzecinkowy lub podwójny ... więc rzutowanie na 'int', aby głupi iostream zrozumiał, że tematem są liczby, a nie znaki, nie pomoże.

Po otrzymaniu negatywnej opinii na moją odpowiedź, może jeszcze kilka słów wyjaśnienia ... Projekt IOStream jest wadliwy, ponieważ nie daje programiście możliwości określenia JAK przedmiot jest traktowany. Implementacja IOStream podejmuje arbitralne decyzje (takie jak traktowanie uint8_t jako znaku, a nie liczby bajtów). To jest wada projektu IOStream, ponieważ próbują osiągnąć nieosiągalne.

C ++ nie pozwala na klasyfikację typu - język nie ma takiej możliwości. Nie ma czegoś takiego jak is_number_type () lub is_character_type (), którego IOStream mógłby użyć do dokonania rozsądnego automatycznego wyboru. Ignorowanie tego i próba uniknięcia zgadywania JEST wadą konstrukcyjną biblioteki.

Przyjęto, że printf () równie nie zadziałaby w ogólnej implementacji „ShowVector ()”. Ale to nie usprawiedliwia zachowania iostream. Ale jest bardzo prawdopodobne, że w przypadku printf () ShowVector () zostałby zdefiniowany w ten sposób:

template <class _T>
void ShowVector( const char *formatString, const char *title, const std::vector<_T> &v );
BitTickler
źródło
3
Wina nie leży (wyłącznie) po stronie iostream. Sprawdź do czego uint8_tsłuży typedef . Czy to właściwie char? Więc nie wiń iostreams, że traktują to jak char.
Martin Ba
A jeśli chcesz mieć pewność, że otrzymasz liczbę w kodzie ogólnym, możesz użyć num_putaspektu zamiast operatora wstawiania strumienia.
Martin Ba
@Martin Ba Masz rację - standardy c / c ++ utrzymują, ile bajtów ma "krótki int bez znaku". „unsigned char” jest cechą charakterystyczną języka. Jeśli naprawdę chcesz bajtu, musisz użyć unsigned char. C ++ nie pozwala również na nałożenie ograniczeń na argumenty szablonów - takie jak „tylko liczby”, więc gdybym zmienił implementację ShowVector na proponowane przez ciebie rozwiązanie num_put, ShowVector nie mógł już wyświetlać wektora ciągów, prawda? ;)
BitTickler
1
@Martin Bla: cppreference wspomina, że ​​int8_t jest typem liczby całkowitej ze znakiem o szerokości dokładnie 8 bitów Zgadzam się z autorem, że to dziwne, że otrzymujesz wtedy śmieci, chociaż technicznie można to wytłumaczyć typedef i przeciążeniem typów znaków w iostream . Można go było rozwiązać, mając __int8 prawdziwy typ zamiast typedef.
gast128
Och, w rzeczywistości jest to całkiem łatwe do naprawienia: // Poprawki dla std :: ostream, który zepsuł obsługę typów unsigned / signed / char // i wyświetla 8-bitowe liczby całkowite, jakby były znakami. przestrzeń nazw ostream_fixes {inline std :: ostream & operator << (std :: ostream & os, unsigned char i) {return os << static_cast <unsigned int> (i); } inline std :: ostream & operator << (std :: ostream & os, signed char i) {return os << static_cast <signed int> (i); }} // przestrzeń nazw ostream_fixes
mcv
1

C ++ iostreamy mają wiele wad, jak zauważono w innych odpowiedziach, ale chciałbym zwrócić uwagę na coś na jego obronę.

C ++ jest praktycznie wyjątkowy wśród poważnie używanych języków, dzięki czemu zmienne dane wejściowe i wyjściowe są proste dla początkujących. W innych językach dane wejściowe użytkownika zwykle obejmują wymuszanie typu lub elementy formatujące ciąg, podczas gdy C ++ sprawia, że ​​kompilator wykonuje całą pracę. To samo dotyczy głównie danych wyjściowych, chociaż C ++ nie jest tak wyjątkowy pod tym względem. Mimo to możesz całkiem dobrze wykonywać sformatowane operacje we / wy w C ++ bez konieczności rozumienia klas i pojęć zorientowanych obiektowo, co jest przydatne pedagogicznie i bez konieczności rozumienia składni formatu. Ponownie, jeśli uczysz początkujących, to duży plus.

Ta prostota dla początkujących ma swoją cenę, która może przyprawić o ból głowy w przypadku radzenia sobie z I / O w bardziej złożonych sytuacjach, ale miejmy nadzieję, że w tym momencie programista nauczył się wystarczająco dużo, aby móc sobie z nimi poradzić, lub przynajmniej stał się wystarczająco stary pić.

user2310967
źródło