C++ bez cholesterolu

Użytkownik:Znajomość C jest warunkiem koniecznym uczenia się C++, prawda?
Stroustrup:Nieprawda. Wspólny podzbiór C i C++ jest łatwiejszy do nauczenia się, niż C. Mniej typów błędów jest koniecznych do ręcznego wyłapywania (system typów C++ jest bardziej ścisły i ekspresywny), mniej sztuczek do uczenia się (C++ pozwala na wyrażanie wielu rzeczy bez stosowania obejść) i dostępne lepsze biblioteki. Najlepsza część wstępna do nauczenia się w C++ to nie jest "cały C".


Zdaję sobie sprawę z tego, że mnóstwo już ludzi zajęło się pisaniem książek nt. C++ (i robieniem na tym pieniędzy). Wiem także, ze nie mam co podskakiwać do takich ludzi jak np. pan Jan B. Niemniej chciałem jedynie zareagować zlekka na pewna dość niedobrą tendencję co do pewnych mniemań nt. języka C++, które chciałbym tu obalić.

Dlaczego "bez cholesterolu" ? Przede wszystkim - bo to jest ostatnio modne ;*). A tak na poważnie - dlatego, ze wciąż panuje dość szeroko rozpowszechniona (acz fałszywa) opinia, że C jest łatwiejszy od C++. W wyniku tego wielu amatorów programowania zajęło sie C, a nie C++, a potem to się im nie chce przestawić. Z kolei po pobieżnym zapoznaniu się z C++ są przekonani, że "C++ posiada narzuty w stosunku do C" (kolejna bzdura rozpowszechniona wśród "znawców" C). Poza tym zwiedzeni tym, że C ma mniej teorii i "definicyjnie" jest dużo mniejszy od C++, stwierdzali że na pełne opanowanie języka C poświęcą mniej czasu. To wszystko można niestety z dostatecznym przybliżeniem traktować jako Trzecią Prawdę Tischnera. Powód jest taki, że ani C, ani C++ nie da się nigdy opanować "w pełni" w rozsądnie krótkim czasie. Zawsze pozostaną jakieś niewyjaśnione niuanse. Owszem, C++ ma ich znacznie więcej, ale gdyby porównać części wspólne C i C++ oraz różne "drobne" dodatki, które należy stosować w programowaniu (w obu językach), to niestety zarówno jeśli chodzi o ilość poświęconego czasu na naukę, jak i łatwość używania wypada to zdecydowanie na niekorzyść C. Ale o tym to wiedzą tylko ci, którzy naprawdę cokolwiek wiedzą o C++. Nie można też zapominać, że oba te języki są jedynie szkieletami, wypełnianymi dopiero przez odpowiednie biblioteki. Ale możliwości bibliotek (zarówno pod względem efektywności i możliwości jakie dają, jak i łatwości ich użytkowania i PRZYSWOJENIA) zależą przecież od możliwości języka. Tak też właśnie to możliwościom C++ zawdzięczamy to, że istnieje składnia obsługi napisów i plików, przesyłania danych pomiędzy strumieniami itp., która jest prosta, wygodniejsza, mniej wrażliwa na błędy, a przede wszystkim - łatwiejsza do zrozumienia dla nowicjusza (a z kolei bardziej zaawansowane warianty - dające większe możliwości wykwalifikowanemu programiście). Przykładowo, kiedy nowicjuszowi mówi się o operowaniu na zmiennych tekstowych, można poprzestać na podstawowych wiadomościach o typie `string'. Opuszcza się w ten sposób spory kawał dość trudnego na początek tematu, a jednocześnie początkujący od razu może pobawić się zmiennymi tekstowymi. Podobnie, wysyłanie tekstów i liczb do strumienia jest łatwiejsze do objaśnienia, niż tłumaczenie, po co jest to całe %d, %s i dlaczego printf jest taka skomplikowana (owszem, formatowanie w stylu printf jest o niebo wygodniejsze, ale na początkowym etapie, jak i często w praktyce, nikt żadnego zaawansowanego formatowania nie potrzebuje). W C bez wyłożenia dokładnych informacji o tym, co to są tablice, jak jest zbudowany string i czego się dla nich używa (co nie jest ani krótkim, ani łatwym tematem) zmiennych tekstowych używać się nie da, podobnie jak poważniejsze zastosowania printf są niemożliwe bez denerwującego określania wprost, jakiego typu jest wypisywana wartość (o pomyłkę tu nietrudno, przy scanf jeszcze bardziej, a ze znanych mi kompilatorów tylko gcc dokonuje sprawdzenia poprawności użycia tych funkcji). Wśród powodów zaś, by uczyć się C, a nie C++, wymienia się też m.in.:

Pozwolę sobie je skomentować.

Wiele udogodnień C++ w zakresie tych samych możliwości, które daje C (czyli jako "lepszy C"), powodują dużą wygodę w pisaniu programów. Wśród nich np. brak konieczności używania słów kluczowych enum/union/struct (i class) przed nazwą typów (a może ktoś chciałby powiedzieć, że rzadko w programach w C tą niedogodność "obchodzi" się przez typedef?). Brak konieczności zdefiniowania wszystkich zmiennych lokalnych przed pierwszą instrukcją w funkcji daje większą swobodę. Nie utrudnia to wcale szukania zmiennych wbrew pozorom; można dzięki temu np. instrukcje sprawdzające warunki wstępne umieścić przed deklaracją wszystkich zmiennych, które tam nie są potrzebne, co powiększa spójność kodu i odpowiadających mu zmiennych. Konieczność "mozolnego szukania" takich zmiennych jest fikcją: po pierwsze programista powinien - mając taką możliwosć - rozmieszczać zmienne jak najsensowniej, a po drugie dzisiejsze narzędzia programistyczne pozwalają na wyszukiwanie w funkcji deklaracji odpowiednich zmiennych lokalnych. Istnieje też zresztą zasada nie tworzenia zbyt długich funkcji. Poza tym, istnieje też możliwość definiowania zmiennych w wyrażeniach w while i for, które mają zasięg lokalny dla podległej im instrukcji; daje to większe możliwości lokalizacji zmiennych. Dodatkowo, wiele osób narzeka na brak pascalowego `with', które tu można symulować zadeklarowaniem odpowiedniej metody wewnątrz struktury.

Co do ograniczania - faktycznie, C++ nie pozwala na niejawne konwersje wskaźnika void* na dowolny wskaźnik. Ale takie konwersje są z reguły idiotyczne, a ta właściwość w C jest możliwa (biorąc pod uwagę, że konwersje pomiędzy innymi wskaźnikami są sprawdzane), aby ułatwić używanie funkcji malloc (calloc i realloc). Dodatkowo dano tę możliwość, żeby wprowadzić symbol NULL, który i tak jest w C potrzebny mniej więcej tak samo, jak biuro przepustek w 5-osobowej firmie (dla C++ istnieje jego sensowna definicja). Zresztą - ileż to razy się widziało, jak ta stała była traktowana jako wartość typu char (mająca sygnować '\0') i kompilator nawet nie miałknął. A co niby nam daje to, że może on być deklarowany jako (void*)0 poza tym, że można go podawać do funkcji o zmiennej liście parametrów? Standard nie precyzuje, że musi on posiadać taką definicję (tak ta definicja CZĘSTO WYGLĄDA, ale standard niczego takiego nie nakazuje, bywa ono też deklarowane jako 0L); z kolei koncepcyjne "odróżnianie" zera całkowitego i wskaźnikowego potrzebne jest tylko ludziom, którzy nigdy nie potrafią spamiętać, co zwraca dana funkcja lub jakiego typu jest dana zmienna (a tacy ludzie z reguły powinni zmienić zajęcie). Inna sprawa w C++ - tam istnieje na tym polu problem, związany z przeciążaniem funkcji (ale też nie jest to konieczna do używania właściwość - wywołania konstruktorów można zastąpić wrapperami w postaci funkcji inline). Innym ograniczaniem jest "czepianie się" o mieszanie znakowości (signedness) wartości całkowitych. Pomieszanie tych rzeczy jest pomieszaniem koncepcyjnym i w efekcie nieporządnym i bałaganiarskim programowaniem, mogącym się stać przyczyną wielu poważnych błędów (indeksowanie tablicy, wartość "uniwersalna", argumenty operacji MNOŻENIA I DZIELENIA, ale najbardziej sławetne operatory przesunięć bitowych). Nawiasem mówiąc, większość rzeczy, które są niekompatybilnością C++ w stosunku do C, są odwiecznie wyklętymi metodami nieporządnego programowania. Przykładowo wszystkie przykłady zamieszczone w książce "Język ANSI C" Kernighana i Ritchie'ego są prawidłowe w języku C++.

Co do braku kontroli z kolei, to jest to po prostu bzdura. Wśród języków udostępniających możliwości obiektowe (puryści obiektowi twierdzą, że C++ nie zasługuje na taką nazwę), C++ jest jedynym, który pozwala na zachowanie pełnej kontroli nad kodem: opcjonalny polimorfizm (oraz polimorfizm parametryczny), opcjonalne wyjątki (mimo istnienia ich w bibliotece standardowej, można odpowiednią opcją kompilatora je wyłączyć, wymuszjąc inną akcję w przypadku wystąpienia sytuacji wyjątkowej), brak obowiązkowego odśmiecacza pamięci - te wszystkie czynniki w większości języków obiektowych są standardowe i obowiązkowe. W C++ zawsze panowała zasada "nie płać za to, czego nie używasz" i do tej pory C++ w pełni tę zasadę realizuje. Pozwala to zachować nie tylko kontrolę nad tym, co program robi, ale też JAK to robi. Zupełnie inaczej jest to w innych językach obiektowych - np. w kompilatorze `POC' języka Objective-C dodano możliwość traktowania "bloków kodu" jako obiektów i w tych blokach można używać też zmiennych lokalnych używanych w funkcji. Nie wiem tylko, czy ich entuzjaści są świadomi tego, że każdy blok jest osobną funkcją (i to nawet nie inline), a zmienne lokalne funkcji posiadającej choć jeden blok, który korzysta ze zmiennych lokalnych, są przydzielane nie na stosie, ale ze sterty (a więc wielokrotnie wolniej). Oczywiście nie każda biblioteka C++ pozwala na taką pełną kontrolę, ale to już nie jest wina C++.

Co ma oznaczać więc "C++ bez cholesterolu" ? Chciałbym po prostu przedstawić ten język i jego właściwości stopniowo: najpierw te właściwości, które czynią go "lepszym C", potem wstępne właściwości obiektowe (które pozwalają też używać go jako "lepszy C"), później bardziej właściwości bardziej obiektowe.

Zatem, zapraszam do lektury ;*)

Wszelkie wnioski i uwagi nt. tej strony proszę kierować do mnie: sektor@kis.p.lodz.pl

1. Przedmowa

2. Właściwości podstawowe C++

3. Właściwości dodatkowe C++

4. Programowanie obiektowe w C++

5. Dodatki

Drobna uwaga: stronke ta zrobil swego czasu sgml2html. Jak zobaczylem, co ten program nawyprawial, czym predzej odsunalem go od tego zaszczytnego zadania i recznie wnioslem wiekszosc poprawek. Niestety zaowocowalo to paroma niekonsekwencjami. Mam nadzieje, ze wiekszosc bledow udalo mi sie usunac, ale np. linki na dole strony (za wyjatkiem "Index", ktory jest wszedzie taki sam) moga dzialac nieprawidlowo i zawierac odwolania do nieistniejacych stron, radzilbym ich zatem nie uzywac (chwilowo nie mam czasu na ich poprawienie, postaram sie tym zajac w najblizszym wolnym czasie).