Używam dużo do printf
celów śledzenia / logowania w moim kodzie, okazało się, że jest to źródło błędu programistycznego. Zawsze uważałem operator wstawiania ( <<
) za coś dziwnego, ale zaczynam myśleć, że używając go zamiast tego mogę uniknąć niektórych z tych błędów.
Czy ktoś miał kiedyś podobne objawienie, czy po prostu chwytam się tutaj słomek?
Niektórzy odbierają punkty
- Mój obecny sposób myślenia jest taki, że bezpieczeństwo typu przewyższa wszelkie korzyści wynikające z używania printf. Prawdziwym problemem jest łańcuch formatu i użycie funkcji variadic, które nie są bezpieczne dla typu.
- Może nie będę używać
<<
i wariantów strumienia wyjściowego stl, ale z pewnością przyjrzę się zastosowaniu mechanizmu bezpiecznego dla typu, który jest bardzo podobny. - Dużo śledzenia / rejestrowania jest warunkowe, ale chciałbym zawsze uruchamiać kod, aby nie pomijać błędów w testach tylko dlatego, że jest to rzadko brana gałąź.
printf
w świecie C ++? Coś tu brakuje?printf
w C ++ jest całkowicie legalne . (Czy to dobry pomysł, to kolejne pytanie.)printf
ma pewne zalety; zobacz moją odpowiedź.Odpowiedzi:
printf, szczególnie w przypadkach, w których możesz dbać o wydajność (takich jak sprintf i fprintf), to naprawdę dziwny hack. Ciągle mnie zadziwia, że ludzie, którzy używają C ++ z powodu niewielkiego narzutu związanego z wydajnością funkcji wirtualnych, będą następnie bronić C's io.
Tak, aby obliczyć format naszego wyjścia, coś, co możemy poznać w 100% w czasie kompilacji, przeanalizujmy ciąg formatu fricken w czasie wykonywania wewnątrz ogromnie dziwnej tabeli skoków, używając niepoprawnych kodów formatu!
Oczywiście tych kodów formatu nie można dopasować do reprezentowanych przez nich typów, byłoby to zbyt łatwe ... i za każdym razem, gdy sprawdzasz, czy to% llg czy% lg, że ten (silnie napisany) język sprawia, że jesteś wymyśl typy ręcznie, aby coś wydrukować / zeskanować, ORAZ został zaprojektowany dla procesorów starszych niż 32-bitowe.
Przyznaję, że obsługa szerokości i precyzji formatu przez C ++ jest nieporęczna i mogłaby zużywać trochę cukru syntaktycznego, ale to nie znaczy, że musisz bronić dziwnego hacka, który jest głównym systemem io. Absolutne podstawy są dość łatwe w każdym języku (chociaż prawdopodobnie powinieneś używać czegoś takiego jak niestandardowa funkcja błędu / strumień błędów w kodzie debugowania), umiarkowane przypadki są podobne do wyrażeń regularnych w C (łatwe do napisania, trudne do analizy / debugowania ), a złożone przypadki niemożliwe w C.
(Jeśli w ogóle korzystasz ze standardowych kontenerów, napisz sobie kilka przeciążeń szybkiego operatora <<, które pozwalają ci robić takie rzeczy jak
std::cout << my_list << "\n";
debugowanie, gdzie moja_lista jest typulist<vector<pair<int,string> > >
).źródło
operator<<(ostream&, T)
przez wywołanie ... no cóżsprintf
! Wydajnośćsprintf
nie jest optymalna, ale z tego powodu wydajność iostreamów jest na ogół jeszcze gorsza.Mieszanie danych wyjściowych w stylu C
printf()
(lubputs()
lubputchar()
...) z danymistd::cout << ...
wyjściowymi w stylu C ++ może być niebezpieczne. Jeśli dobrze pamiętam, mogą mieć osobne mechanizmy buforowania, więc dane wyjściowe mogą nie pojawiać się w zamierzonej kolejności. (Jak wspomina AProgrammer w komentarzu,sync_with_stdio
rozwiązuje ten problem).printf()
jest zasadniczo niebezpieczny dla typu. Typ oczekiwany dla argumentu jest określony przez ciąg formatu ("%d"
wymagaint
promowania lub czegoś, co promujeint
,"%s"
wymaga,char*
który musi wskazywać na poprawnie zakończony ciąg w stylu C itp.), Ale przekazanie niewłaściwego typu argumentu powoduje niezdefiniowane zachowanie , nie jest to błąd diagnostyczny. Niektóre kompilatory, takie jak gcc, wykonują dość dobrą robotę, ostrzegając przed niedopasowaniem typów, ale mogą to zrobić tylko wtedy, gdy ciąg formatu jest literałem lub jest znany w czasie kompilacji (co jest najczęstszym przypadkiem) - i takie ostrzeżenia nie są wymagane przez język. Jeśli podasz niewłaściwy typ argumentu, mogą się zdarzyć dowolne złe rzeczy.Z drugiej strony, strumień I / O C ++ jest znacznie bardziej bezpieczny dla typu, ponieważ
<<
operator jest przeciążony wieloma różnymi typami.std::cout << x
nie musi określać typux
; kompilator wygeneruje odpowiedni kod dla dowolnego typux
.Z drugiej strony
printf
opcje formatowania są znacznie wygodniejsze. Jeśli chcę wydrukować wartość zmiennoprzecinkową z 3 cyframi po przecinku, mogę użyć"%.3f"
- i nie ma to wpływu na inne argumenty, nawet w ramach tego samegoprintf
wywołania.setprecision
Z drugiej strony C ++ wpływa na stan strumienia i może zepsuć później dane wyjściowe, jeśli nie będziesz bardzo ostrożny, aby przywrócić strumień do poprzedniego stanu. (To moje osobiste wkurzenie; jeśli brakuje mi jakiegoś czystego sposobu, aby tego uniknąć, proszę o komentarz.)Oba mają zalety i wady. Dostępność
printf
jest szczególnie przydatna, jeśli masz tło C i znasz go bardziej lub importujesz kod źródłowy C do programu C ++.std::cout << ...
jest bardziej idiomatyczny dla C ++ i nie wymaga tyle uwagi, aby uniknąć niedopasowania typów. Oba są poprawnymi językami C ++ (standard C ++ obejmuje większość biblioteki standardowej C przez odniesienie).To prawdopodobnie najlepiej użyć
std::cout << ...
dla dobra innych programistów C ++, który może pracować na kodzie, ale można użyć jednego albo - zwłaszcza w kodzie śledzenia, że masz zamiar wyrzucić.Oczywiście warto poświęcić trochę czasu na naukę korzystania z debuggerów (ale w niektórych środowiskach może to nie być możliwe).
źródło
printf
.std::ios_base::sync_with_stdio
.std::cout
używa osobnego wywołania dla każdego drukowanego elementu? Możesz obejść ten problem, zbierając wiersz wyniku w łańcuch przed wydrukowaniem go. I oczywiście możesz również wydrukować jeden element na razprintf
; wygodniej jest wydrukować linię (lub więcej) w jednym połączeniu.Twój problem najprawdopodobniej pochodzi z mieszania dwóch bardzo różnych standardowych menedżerów produkcji, z których każdy ma swój własny program dla tego biednego małego STDOUT. Nie otrzymujesz żadnych gwarancji dotyczących sposobu ich implementacji, i jest całkowicie możliwe, że ustawiają one opcje deskryptora powodującego konflikt, obie próbują zrobić z nim różne rzeczy itp. Ponadto operatorzy wstawiania mają jedną poważną nad
printf
:printf
pozwolą ci to zrobić:Podczas gdy
<<
nie będzie.Uwaga: Do debugowania nie używasz
printf
anicout
. Używaszfprintf(stderr, ...)
icerr
.źródło
printf
nie jest on bezpieczny dla typu, a obecnie myślę, że bezpieczeństwo typu przewyższa wszelkie korzyści wynikające z używaniaprintf
. Problemem jest tak naprawdę ciąg formatujący i funkcja variadic, która nie jest bezpieczna dla typu.SomeObject
będę wskaźnikiem? Otrzymasz dowolne dane binarne reprezentowane przez kompilatorSomeObject
.Istnieje wiele grup - na przykład Google - które nie lubią strumieni.
http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams
(Pop otwórz trójkąt, żebyś mógł zobaczyć dyskusję.) Myślę, że przewodnik po stylu Google C ++ zawiera DUŻO bardzo rozsądnych porad.
Myślę, że kompromis polega na tym, że strumienie są bezpieczniejsze, ale printf jest bardziej czytelny (i łatwiejszy do uzyskania pożądanego formatowania).
źródło
printf
może powodować błędy z powodu braku bezpieczeństwa typu. Istnieje kilka sposobów adresowania, że bez przełączaniaiostream
„s<<
operatora i bardziej skomplikowane formatowanie:printf
ciągi formatu względemprintf
argumentów i mogą wyświetlać ostrzeżenia, takie jak poniższe, jeśli się nie zgadzają.printf
-Style połączenia, aby je wpisać bezpieczny.printf
ciągów formatu podobnych (w szczególności Boost.Format są prawie identyczneprintf
), przy jednoczesnym zachowaniuiostreams
bezpieczeństwa i rozszerzalności typu.źródło
Składnia Printf jest w zasadzie w porządku, pomijając pewne niejasne pisanie. Jeśli uważasz, że to źle, dlaczego C #, Python i inne języki używają bardzo podobnej konstrukcji? Problem w C lub C ++: nie jest częścią języka i dlatego nie jest sprawdzany przez kompilator pod kątem poprawnej składni (*) i nie jest rozkładany na serię wywołań natywnych, jeśli optymalizuje się pod kątem szybkości. Pamiętaj, że jeśli zoptymalizujesz rozmiar, wywołania printf mogą okazać się bardziej wydajne! Składnia przesyłania strumieniowego w C ++ jest imho niezbyt dobra. Działa, bezpieczeństwo typu istnieje, ale pełna składnia ... bleh. Mam na myśli, że go używam, ale bez radości.
(*) niektóre kompilatory wykonują to sprawdzanie oraz prawie wszystkie narzędzia analizy statycznej (używam Lint i od tamtej pory nigdy nie miałem problemów z printf).
źródło
format("fmt") % arg1 % arg2 ...;
) z bezpieczeństwem typu. Kosztem większej wydajności, ponieważ generuje wywołania typu stringstream, które wewnętrznie generują wywołania sprintf w wielu implementacjach.printf
jest, moim zdaniem, znacznie bardziej elastycznym narzędziem wyjściowym do radzenia sobie ze zmiennymi niż którekolwiek wyjście strumienia CPP. Na przykład:Jednak możesz użyć
<<
operatora CPP , gdy przeciążasz go dla określonej metody ... na przykład, aby uzyskać zrzut obiektu, który przechowuje dane konkretnej osoby,PersonData
....W związku z tym bardziej efektywne byłoby stwierdzenie (zakładając, że
a
jest to obiekt PersonData)niż:
Ten pierwszy jest o wiele bardziej zgodny z zasadą enkapsulacji (nie trzeba znać szczegółów, prywatnych zmiennych członkowskich), a także jest łatwiejszy do odczytania.
źródło
Nie powinieneś używać
printf
w C ++. Zawsze. Powodem jest, jak słusznie zauważyłeś, że jest to źródło błędów, a fakt, że drukowanie typów niestandardowych, aw C ++ prawie wszystko powinno być typami niestandardowymi, jest bólem. Rozwiązaniem w C ++ są strumienie.Istnieje jednak poważny problem, który sprawia, że strumienie nie są odpowiednie dla żadnego wyjścia widocznego dla użytkownika! Problemem są tłumaczenia. Przykład zapożyczenia z podręcznika gettext mówi, że chcesz napisać:
Teraz przychodzi niemiecki tłumacz i mówi: Ok, po niemiecku, wiadomość powinna być
A teraz masz kłopoty, ponieważ on potrzebuje porozrzucanych elementów. Należy powiedzieć, że nawet wiele wdrożeń
printf
ma z tym problem. Chyba że obsługują rozszerzenie, więc możesz użyćW Boost.Format obsługuje formaty printf stylu i ma tę funkcję. Więc piszesz:
Niestety, wiąże się to z pewnym obniżeniem wydajności, ponieważ wewnętrznie tworzy ciąg znaków i używa
<<
operatora do formatowania każdego bitu, aw wielu implementacjach<<
operator wewnętrznie wywołujesprintf
. Podejrzewam, że bardziej skuteczne wdrożenie byłoby możliwe, gdyby było to naprawdę pożądane.źródło
Wykonujesz wiele bezużytecznej pracy, oprócz tego, że
stl
jest zły lub nie, debuguj swój kod za pomocą seriiprintf
tylko 1 dodatkowego poziomu możliwych awarii.Wystarczy użyć debuggera i przeczytać coś o wyjątkach oraz o tym, jak je złapać i wyrzucić; staraj się nie być bardziej gadatliwym niż w rzeczywistości.
PS
printf
jest używany w C, dla C ++ maszstd::cout
źródło