Rozważmy następujące przykłady Hello World w C i C ++:
#include <stdio.h>
int main()
{
printf("Hello world\n");
return 0;
}
#include <iostream>
int main()
{
std::cout<<"Hello world"<<std::endl;
return 0;
}
Kiedy kompiluję je w godbolt do assemblacji, rozmiar kodu C wynosi tylko 9 linii ( gcc -O3
):
.LC0:
.string "Hello world"
main:
sub rsp, 8
mov edi, OFFSET FLAT:.LC0
call puts
xor eax, eax
add rsp, 8
ret
Ale rozmiar kodu C ++ to 22 linie ( g++ -O3
):
.LC0:
.string "Hello world"
main:
sub rsp, 8
mov edx, 11
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
xor eax, eax
add rsp, 8
ret
_GLOBAL__sub_I_main:
sub rsp, 8
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
add rsp, 8
jmp __cxa_atexit
... który jest znacznie większy.
Wiadomo, że w C ++ płacisz za to, co jesz. Więc w takim razie za co płacę?
eat
związanego z C ++. Myślę, że masz na myśli: „Płacisz tylko za to, czego używasz ”?eat
jest bardziej niejednoznaczny i należy go unikać.Odpowiedzi:
To, za co płacisz, to wywołanie ciężkiej biblioteki (nie tak ciężkiej jak drukowanie na konsoli). Inicjalizujesz
ostream
obiekt. Jest trochę ukrytej pamięci. Wtedy wzywasz,std::endl
co nie jest synonimem\n
. Plikiostream
Biblioteka pomaga dostosowanie wielu ustawień i wprowadzenie obciążenia procesora zamiast programatora. Za to płacisz.Przejrzyjmy kod:
Inicjalizacja obiektu ostream + cout
Dzwonię
cout
ponownie, aby wydrukować nową linię i opróżnićInicjalizacja pamięci statycznej:
Niezbędne jest również rozróżnienie między językiem a biblioteką.
A tak przy okazji, to tylko część historii. Nie wiesz, co jest napisane w wywołanych funkcjach.
źródło
cout; printf; cout
zapisuje w kolejności (ponieważ mają własne bufory). Drugi spowoduje desynchronizacjęcout
icin
, w pierwszej kolejności ,cout; cin
potencjalnie poprosi użytkownika o informacje. Płukanie zmusi go do synchronizacji tylko wtedy, gdy jest to naprawdę potrzebne.std::cout
jest potężniejszy i bardziej skomplikowany niżprintf
. Obsługuje takie elementy, jak ustawienia regionalne, stanowe flagi formatowania i inne.Jeśli ich nie potrzebujesz, użyj
std::printf
lubstd::puts
- są dostępne w<cstdio>
.Chcę również wyjaśnić, że C ++ ! = Biblioteka standardowa C ++. Biblioteka standardowa ma być uniwersalna i „wystarczająco szybka”, ale często będzie wolniejsza niż wyspecjalizowana implementacja tego, czego potrzebujesz.
Z drugiej strony język C ++ stara się umożliwić pisanie kodu bez ponoszenia niepotrzebnych dodatkowych ukrytych kosztów (np. Opt-in
virtual
, brak czyszczenia pamięci).źródło
Nie porównujesz C i C ++. Porównujesz
printf
istd::cout
, które są zdolne do różnych rzeczy (ustawienia regionalne, formatowanie stanowe itp.).Spróbuj użyć następującego kodu do porównania. Godbolt generuje ten sam zestaw dla obu plików (testowany z gcc 8.2, -O3).
main.c:
main.cpp:
źródło
Twoje aukcje rzeczywiście porównują jabłka i pomarańcze, ale nie z powodu podanego w większości innych odpowiedzi.
Sprawdźmy, co właściwie robi Twój kod:
DO:
"Hello world\n"
C ++:
"Hello world"
dostd::cout
std::endl
manipulator dostd::cout
Wygląda na to, że twój kod C ++ wykonuje dwa razy więcej pracy. Dla uczciwego porównania powinniśmy połączyć to:
… I nagle twój kod asemblera
main
wygląda bardzo podobnie do C:W rzeczywistości możemy porównać kod C i C ++ wiersz po wierszu i jest bardzo niewiele różnic :
Jedyną prawdziwą różnicą jest to, że w C ++ wywołujemy
operator <<
z dwoma argumentami (std::cout
i łańcuchem). Moglibyśmy usunąć nawet tę niewielką różnicę, używając bliższego C ekwiwalent:,fprintf
który również ma pierwszy argument określający strumień.To pozostawia kod asemblera
_GLOBAL__sub_I_main
, który jest generowany dla C ++, ale nie dla C. To jest jedyny prawdziwy narzut, który jest widoczny w tym zestawieniu (jest więcej, niewidoczny narzut dla obu języków, oczywiście). Ten kod wykonuje jednorazową konfigurację niektórych funkcji biblioteki standardowej C ++ na początku programu C ++.Ale, jak wyjaśniono w innych odpowiedziach, istotna różnica między tymi dwoma programami nie zostanie znaleziona w wyniku montażu
main
funkcji, ponieważ całe ciężkie podnoszenie odbywa się za kulisami.źródło
_start
ale jej kod jest częścią biblioteki wykonawczej C. W każdym razie dzieje się tak zarówno dla C, jak i C ++.std::cout
a zamiast tego przekazuje I / O do implementacji stdio (która używa własnych mechanizmów buforowania). W szczególności, po podłączeniu (co jest znane) do interaktywnego terminala, domyślnie nigdy nie zobaczysz w pełni buforowanych danych wyjściowych podczas pisania dostd::cout
. Musisz jawnie wyłączyć synchronizację ze stdio, jeśli chcesz, aby biblioteka iostream używała własnych mechanizmów buforowaniastd::cout
.printf
nie trzeba tu przepłukiwać strumieni. W rzeczywistości, w typowym przypadku użycia (wyjście przekierowane do pliku), zwykle zauważysz, żeprintf
instrukcja nie jest opróżniana. Tylko wtedy, gdy wyjście jest buforowane liniowo lub niebuforowane,printf
wyzwala flush.To proste. Płacisz za
std::cout
. „Płacisz tylko za to, co jesz” nie oznacza „zawsze dostajesz najlepsze ceny”. Jasne,printf
jest tańsze. Można argumentować, żestd::cout
jest bezpieczniejszy i bardziej wszechstronny, stąd jego większy koszt jest uzasadniony (kosztuje więcej, ale zapewnia większą wartość), ale to mija się z celem. Nie używaszprintf
, używaszstd::cout
, więc płacisz za używaniestd::cout
. Nie płacisz za używanieprintf
.Dobrym przykładem są funkcje wirtualne. Funkcje wirtualne mają pewne wymagania związane z kosztami czasu działania i przestrzenią - ale tylko wtedy, gdy faktycznie ich używasz. Jeśli nie korzystasz z funkcji wirtualnych, nic nie płacisz.
Kilka uwag
Nawet jeśli kod C ++ wymaga większej liczby instrukcji asemblacyjnych, nadal jest to garść instrukcji, a wszelkie narzuty wydajnościowe są prawdopodobnie ograniczone przez rzeczywiste operacje we / wy.
Właściwie czasami jest to nawet lepsze niż „w C ++ płacisz za to, co jesz”. Na przykład kompilator może wywnioskować, że wywołanie funkcji wirtualnej nie jest potrzebne w pewnych okolicznościach i przekształcić to w wywołanie niewirtualne. Oznacza to, że możesz bezpłatnie korzystać z funkcji wirtualnych . Czy to nie wspaniałe?
źródło
"Lista asemblera dla printf" NIE jest przeznaczona dla printf, ale dla puts (rodzaj optymalizacji kompilatora?); printf jest o wiele bardziej skomplikowany niż puts ... nie zapomnij!
źródło
std::cout
wnętrznościach, których nie widać na liście montażowej.puts
, które wygląda identycznie jak wywołanie,printf
jeśli przekażesz tylko jeden ciąg formatu i zero dodatkowych argumentów. (poza tym, że będzie również,xor %eax,%eax
ponieważ przekazujemy zero argumentów FP w rejestrach do funkcji wariadycznej). Żadne z nich nie jest implementacją, po prostu przekazuje wskaźnik do ciągu znaków do funkcji bibliotecznej. Ale tak, optymalizacjaprintf
podputs
kątem jest czymś, co gcc robi dla formatów, które mają tylko"%s"
lub gdy nie ma konwersji, a ciąg kończy się znakiem nowej linii.Widzę tutaj kilka ważnych odpowiedzi, ale zamierzam zagłębić się w szczegóły.
Przejdź do poniższego podsumowania, aby znaleźć odpowiedź na swoje główne pytanie, jeśli nie chcesz przeglądać całej ściany tekstu.
Abstrakcja
Płacisz za abstrakcję . Możliwość pisania prostszego i bardziej przyjaznego dla człowieka kodu ma swoją cenę. W C ++, który jest językiem zorientowanym obiektowo, prawie wszystko jest obiektem. Kiedy używasz dowolnego przedmiotu, pod maską zawsze będą się dziać trzy główne rzeczy:
init()
metodą). Zwykle alokacja pamięci odbywa się pod maską, jako pierwsza rzecz na tym etapie.Nie widzisz tego w kodzie, ale za każdym razem, gdy używasz obiektu, wszystkie trzy powyższe rzeczy muszą jakoś się wydarzyć. Gdybyś zrobił wszystko ręcznie, kod byłby oczywiście znacznie dłuższy.
Teraz abstrakcję można wydajnie tworzyć bez dodawania narzutu: wbudowywanie metod i inne techniki mogą być używane zarówno przez kompilatory, jak i programistów, aby usunąć narzuty abstrakcji, ale to nie jest Twój przypadek.
Co tak naprawdę dzieje się w C ++?
Oto on, w podziale:
std::ios_base
Klasa jest inicjowany, który jest klasą bazową dla Everything I / O pokrewnych.std::cout
Obiekt jest inicjowany.std::__ostream_insert
, co (jak już zorientowałeś się po nazwie) jest metodąstd::cout
(w zasadzie<<
operator), która dodaje ciąg do strumienia.cout::endl
jest również przekazywany dostd::__ostream_insert
.__std_dso_handle
jest przekazywana do__cxa_atexit
, która jest funkcją globalną odpowiedzialną za „czyszczenie” przed wyjściem z programu.__std_dso_handle
sama jest wywoływana przez tę funkcję do zwalniania i niszczenia pozostałych obiektów globalnych.Więc używając C == nie płacisz za nic?
W kodzie C dzieje się bardzo niewiele kroków:
puts
za pośrednictwemedi
rejestru.puts
zostanie wezwany.Nie ma żadnych obiektów, więc nie ma potrzeby inicjowania / niszczenia czegokolwiek.
To jednak nie znaczy, że nie jesteś „płacić” za nic w C . Wciąż płacisz za abstrakcję, a także za inicjalizację standardowej biblioteki C i dynamiczne rozwiązywanie
printf
funkcji (a właściwieputs
, która jest optymalizowana przez kompilator, ponieważ nie potrzebujesz żadnego ciągu formatu) nadal odbywa się pod maską.Gdybyś napisał ten program w czystym asemblerze, wyglądałby mniej więcej tak:
Co w zasadzie powoduje tylko wywołanie wywołania
write
systemowego, po którym następuje wywołanieexit
systemowe. Teraz to byłoby absolutne minimum, aby osiągnąć to samo.Podsumowując
C jest o wiele bardziej surowy i robi tylko niezbędne minimum, pozostawiając pełną kontrolę użytkownikowi, który jest w stanie w pełni zoptymalizować i dostosować praktycznie wszystko, czego chce. Mówisz procesorowi, aby załadował łańcuch do rejestru, a następnie wywołujesz funkcję biblioteki, aby użyć tego ciągu. Z drugiej strony C ++ jest o wiele bardziej złożony i abstrakcyjny . Ma to ogromną zaletę przy pisaniu skomplikowanego kodu i pozwala na łatwiejszy do napisania i bardziej przyjazny dla człowieka kod, ale oczywiście wiąże się to z kosztami. Zawsze będzie wada wydajności w C ++ w porównaniu z C w takich przypadkach, ponieważ C ++ oferuje więcej niż to, co jest potrzebne do wykonania takich podstawowych zadań, a zatem zwiększa narzut .
Odpowiadając na twoje główne pytanie :
W tym konkretnym przypadku tak . Nie wykorzystujesz niczego, co C ++ ma do zaoferowania więcej niż C, ale to tylko dlatego, że w tym prostym fragmencie kodu nie ma nic, w czym C ++ mógłby ci pomóc: jest tak prosty, że w ogóle nie potrzebujesz C ++.
Aha, i jeszcze jedna rzecz!
Zalety C ++ mogą nie wydawać się oczywiste na pierwszy rzut oka, ponieważ napisałeś bardzo prosty i mały program, ale spójrz na nieco bardziej złożony przykład i zobacz różnicę (oba programy robią dokładnie to samo):
C :
C ++ :
Mam nadzieję, że wyraźnie widzisz, co mam na myśli. Zwróć także uwagę, jak w C musisz zarządzać pamięcią na niższym poziomie, używając
malloc
ifree
jak musisz być bardziej ostrożny przy indeksowaniu i rozmiarach oraz jak musisz być bardzo dokładny podczas przyjmowania danych wejściowych i drukowania.źródło
Na początek jest kilka nieporozumień. Po pierwsze, program C ++ nie daje 22 instrukcji, to raczej 22 000 z nich (wyciągnąłem tę liczbę z kapelusza, ale jest mniej więcej w polu gry). Również kod C nie daje 9 instrukcji. To tylko te, które widzisz.
To, co robi kod C, to po zrobieniu wielu rzeczy, których nie widzisz, wywołuje funkcję z CRT (która jest zwykle, ale niekoniecznie obecna jako biblioteka współdzielona), a następnie nie sprawdza zwracanej wartości ani uchwytu błędy i wyskakuje. W zależności od ustawień kompilatora i optymalizacji, tak naprawdę nawet nie wywołuje,
printf
aleputs
lub coś jeszcze bardziej prymitywnego.Mógłbyś napisać mniej więcej ten sam program (z wyjątkiem niektórych niewidocznych funkcji init) również w C ++, gdybyś wywołał tę samą funkcję w ten sam sposób. Lub, jeśli chcesz być bardzo poprawny, ta sama funkcja z prefiksem
std::
.Odpowiedni kod C ++ w rzeczywistości nie jest tym samym. Podczas gdy całość
<iostream>
jest dobrze znana z tego, że jest grubą, brzydką świnią, która powoduje ogromne obciążenie dla małych programów (w „prawdziwym” programie tak naprawdę nie zauważasz tak dużo), nieco sprawiedliwszą interpretacją jest to, że robi okropnie wiele rzeczy, których nie widzisz i które po prostu działają . W tym między innymi magiczne formatowanie prawie wszystkich przypadkowych rzeczy, w tym różne formaty liczb, ustawienia regionalne i tak dalej, a także buforowanie i właściwa obsługa błędów. Obsługa błędów? No tak, zgadnij co, wypisanie łańcucha może się nie powieść, aw przeciwieństwie do programu w C, program w C ++ nie zignorowałby tego po cichu. Biorąc pod uwagę costd::ostream
robi to pod maską i nikt o tym nie wie, w rzeczywistości jest dość lekki. Nie tak, że go używam, ponieważ z pasją nienawidzę składni strumienia. Ale nadal jest całkiem niesamowite, jeśli weźmiesz pod uwagę, co robi.Ale oczywiście C ++ ogólnie nie jest tak wydajny, jak może być. Nie może być tak wydajne, ponieważ nie jest tym samym i nie robi tego samego. Jeśli nic więcej, C ++ generuje wyjątki (i kod do generowania, obsługi lub niepowodzenia) i daje pewne gwarancje, których C nie daje. Więc oczywiście program w C ++ musi być trochę większy. Jednak w szerszej perspektywie nie ma to żadnego znaczenia. Wręcz przeciwnie, w przypadku prawdziwych programów nierzadko stwierdziłem, że C ++ działa lepiej, ponieważ z tego czy innego powodu wydaje się, że nadaje się do bardziej korzystnych optymalizacji. Nie pytaj mnie w szczególności dlaczego, nie wiedziałbym.
Jeśli zamiast odpalić i zapomnieć o nadziei na najlepsze, chcesz napisać kod C, który jest poprawny (tj. Faktycznie sprawdzasz błędy, a program zachowuje się poprawnie w obecności błędów), różnica jest marginalna, jeśli istnieje.
źródło
std::cout
rzucają wyjątki?std::cout
jest astd::basic_ostream
i którą można zgłosić, i może ponownie zgłosić inaczej występujące wyjątki, jeśli jest do tego skonfigurowana , lub może połknąć wyjątki. Rzecz w tym, że coś może zawieść, a C ++, podobnie jak standardowa biblioteka C ++, jest (w większości) zbudowana, więc awarie nie pozostają niezauważone. To jest irytacja i błogosławieństwo (ale bardziej błogosławieństwo niż irytacja). C z drugiej strony pokazuje tylko środkowy palec. Nie sprawdzasz kodu zwrotnego, nigdy nie wiesz, co się stało.Płacisz za błąd. W latach 80., gdy kompilatory nie były wystarczająco dobre, aby sprawdzić ciągi formatujące, przeciążanie operatorów było postrzegane jako dobry sposób na wymuszenie pewnego pozoru bezpieczeństwa typów podczas operacji io. Jednak każda z funkcji banera jest od samego początku źle zaimplementowana lub koncepcyjnie bankrutująca:
<iomanip>
Najbardziej odrażającą częścią interfejsu API strumienia io w C ++ jest istnienie tej biblioteki nagłówka formatowania. Oprócz tego, że jest stanowy, brzydki i podatny na błędy, łączy formatowanie ze strumieniem.
Załóżmy, że chcesz wydrukować wiersz zawierający 8-cyfrowy znak szesnastkowy wypełniony zerem bez znaku int, po którym następuje spacja, a następnie znak double z 3 miejscami po przecinku. Z
<cstdio>
można odczytać zwięzły ciąg formatu. Za pomocą<ostream>
musisz zapisać stary stan, ustawić wyrównanie w prawo, ustawić znak wypełnienia, ustawić szerokość wypełnienia, ustawić podstawę na wartość szesnastkową, wypisać liczbę całkowitą, przywrócić zapisany stan (w przeciwnym razie formatowanie liczb całkowitych zanieczyści formatowanie zmiennoprzecinkowe), wypisz spację , ustaw notację na ustaloną, ustaw precyzję, wypisz podwójną i nową linię, a następnie przywróć stare formatowanie.Przeciążanie operatorów
<iostream>
jest dzieckiem plakatu pokazującym, jak nie używać przeciążenia operatorów:Występ
std::cout
jest kilka razy wolniejszyprintf()
. Szalejące featuritis i wirtualna wysyłka mają swoje żniwo.Bezpieczeństwo wątku
Obie
<cstdio>
i<iostream>
są bezpieczne wątkowo, ponieważ każde wywołanie funkcji jest niepodzielne. Aleprintf()
za jedno połączenie można zrobić dużo więcej. Jeśli uruchomisz następujący program z<cstdio>
opcją, zobaczysz tylko wierszf
. Jeśli używasz<iostream>
na maszynie wielordzeniowej, prawdopodobnie zobaczysz coś innego.Odpowiedź na ten przykład jest taka, że większość ludzi ćwiczy dyscyplinę, aby i tak nigdy nie pisać do jednego deskryptora pliku z wielu wątków. Cóż, w takim przypadku, trzeba stwierdzić, że
<iostream>
będzie pomocny chwycić blokadę na każdy<<
i każdy>>
. Podczas gdy w<cstdio>
, nie będziesz blokować tak często, a nawet masz możliwość nie blokowania.<iostream>
zużywa więcej zamków, aby uzyskać mniej spójny wynik.źródło
std::cout
Jest kilka razy wolniejszyprintf()
” - to twierdzenie powtarza się w całej sieci, ale nie było to prawdą od wieków. Nowoczesne implementacje IOstream działają na równi zprintf
. Ten ostatni wykonuje również wewnętrznie wirtualną wysyłkę, aby poradzić sobie ze buforowanymi strumieniami i zlokalizowanymi IO (wykonanymi przez system operacyjny, ale mimo to wykonanymi).printf
icout
kurczy się. Nawiasem mówiąc, na tej samej stronie jest mnóstwo takich testów.Oprócz tego, co powiedziały wszystkie inne odpowiedzi,
jest też fakt, że
std::endl
to nie to samo co'\n'
.Jest to niestety powszechne nieporozumienie.
std::endl
nie oznacza „nowej linii”,oznacza „wydrukuj nową linię, a następnie opróżnij strumień ”. Spłukiwanie nie jest tanie!
Całkowicie ignorując przez chwilę różnice między
printf
istd::cout
, aby funkcjonalnie odpowiadać przykładowi w C, przykład w C ++ powinien wyglądać następująco:A oto przykład tego, jak powinny wyglądać twoje przykłady, jeśli uwzględnisz spłukiwanie.
do
C ++
Porównując kod, zawsze powinieneś uważać, aby porównać podobne do podobieństwa i zrozumieć konsekwencje tego, co robi twój kod. Czasami nawet najprostsze przykłady są bardziej skomplikowane, niż niektórym się wydaje.
źródło
std::endl
jest funkcjonalnym odpowiednikiem pisania nowej linii do buforowanego liniowo strumienia standardowego.stdout
w szczególności musi być buforowany przez linię lub niebuforowany po podłączeniu do urządzenia interaktywnego. Wydaje mi się, że Linux nalega na opcję buforowania liniowego.std::endl
do wypisywania nowych linii.setvbuf(3)
? A może chcesz powiedzieć, że domyślnym jest buforowanie linii? FYI: Zwykle wszystkie pliki są buforowane blokowo. Jeśli strumień odwołuje się do terminala (jak zwykle robi to stdout), jest buforowany liniowo. Standardowy strumień błędów stderr jest zawsze domyślnie niebuforowany.printf
spłukuje automatycznie po napotkaniu postaci nowej linii?Chociaż istniejące odpowiedzi techniczne są poprawne, myślę, że ostatecznie pytanie wynika z tego błędnego przekonania:
To tylko marketingowa rozmowa ze społecznością C ++. (Szczerze mówiąc, w każdej społeczności językowej toczą się rozmowy marketingowe). Nie oznacza to niczego konkretnego, na czym można by poważnie polegać.
„Płacisz za to, czego używasz” ma oznaczać, że funkcja C ++ ma narzut tylko wtedy, gdy używasz tej funkcji. Ale definicja „funkcji” nie jest nieskończenie szczegółowa. Często zdarza się, że aktywujesz funkcje, które mają wiele aspektów i nawet jeśli potrzebujesz tylko części tych aspektów, często implementacja nie jest praktyczna lub możliwa do częściowego wprowadzenia funkcji.
Ogólnie rzecz biorąc, wiele języków (choć prawdopodobnie nie wszystkie) stara się być wydajnymi, z różnym skutkiem. C ++ jest gdzieś na skali, ale w jego projekcie nie ma nic specjalnego ani magicznego, co pozwoliłoby mu odnieść sukces w tym celu.
źródło
<cstdio>
i nie uwzględniać<iostream>
, tak jak w przypadku kompilacji-fno-exceptions -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables
.Funkcje wejścia / wyjścia w C ++ są elegancko napisane i zaprojektowane tak, aby były łatwe w użyciu. Pod wieloma względami są wizytówką funkcji zorientowanych obiektowo w C ++.
Ale rzeczywiście w zamian tracisz trochę wydajności, ale jest to nieistotne w porównaniu z czasem potrzebnym systemowi operacyjnemu na obsługę funkcji na niższym poziomie.
Zawsze możesz wrócić do funkcji w stylu C, ponieważ są one częścią standardu C ++, lub być może całkowicie zrezygnować z przenośności i użyć bezpośrednich wywołań do systemu operacyjnego.
źródło
std::basic_*stream
dół), zna nadchodzące eadache. Miały być szeroko rozpowszechnione i rozszerzane poprzez dziedziczenie; ale ostatecznie nikt tego nie zrobił, ze względu na ich złożoność (o iostreamach pisze się dosłownie), do tego stopnia, że powstały nowe biblioteki (np. boost, ICU itp.). Wątpię, czy kiedykolwiek przestaniemy płacić za ten błąd.Jak widzieliśmy w innych odpowiedziach, płacisz, gdy łączysz się w ogólnych bibliotekach i wywołujesz złożone konstruktory. Nie ma tu żadnego szczególnego pytania, a raczej zarzut. Wskażę kilka aspektów z prawdziwego świata:
Barne miał podstawową zasadę projektowania, aby nigdy nie pozwolić, aby wydajność była powodem pozostania w C, a nie w C ++. To powiedziawszy, trzeba być ostrożnym, aby uzyskać te wydajności, a czasami zdarzają się wydajności, które zawsze działały, ale nie były „technicznie” zgodne ze specyfikacją C. Na przykład układ pól bitowych nie został tak naprawdę określony.
Spróbuj przejrzeć ostream. O mój Boże, to nadęty! Nie zdziwiłbym się, gdybym znalazł tam symulator lotu. Nawet printf () z biblioteki stdlib zwykle działa około 50 KB. To nie są leniwi programiści: połowa rozmiaru printf była spowodowana argumentami o pośredniej precyzji, których większość ludzi nigdy nie używa. Prawie każda naprawdę ograniczona biblioteka procesora tworzy własny kod wyjściowy zamiast printf.
Zwiększenie rozmiaru zwykle zapewnia bardziej ograniczone i elastyczne doświadczenie. Analogicznie, automat sprzedający za kilka monet sprzedaje filiżankę substancji przypominającej kawę, a cała transakcja trwa niecałą minutę. Wpadnięcie do dobrej restauracji wymaga nakrycia stołu, siedzenia, zamawiania, czekania, zdobycia dobrej filiżanki, otrzymania rachunku, zapłacenia za wybór form, dodania napiwku i życzenia miłego dnia w drodze. To inne doświadczenie i wygodniejsze, jeśli wpadasz z przyjaciółmi na złożony posiłek.
Ludzie wciąż piszą ANSI C, choć rzadko K&R C. Z mojego doświadczenia wynika, że zawsze kompilujemy go za pomocą kompilatora C ++, używając kilku poprawek konfiguracyjnych, aby ograniczyć to, co jest wciągana. Istnieją dobre argumenty za innymi językami: Go usuwa polimorficzny narzut i szalony preprocesor ; pojawiło się kilka dobrych argumentów za inteligentniejszym pakowaniem w terenie i układem pamięci. IMHO Myślę, że każdy projekt języka powinien zaczynać się od listy celów, podobnie jak Zen w Pythonie .
To była fajna dyskusja. Pytasz, dlaczego nie możesz mieć magicznie małych, prostych, eleganckich, kompletnych i elastycznych bibliotek?
Nie ma odpowiedzi. Nie będzie odpowiedzi. To jest odpowiedź.
źródło