C++: mity i fakty

| Technika

W latach 90. rozpowszechniła się w kręgach zainteresowanych tematyką opinia, że język C++ nie nadaje się do tego, żeby pisać w nim programy dla mikrokontrolerów jednoukładowych. W przypadku małych systemów 8- i 16-bitowych, dominujących w tamtym okresie, jest to zapewne prawda. Ale dziś, w dobie tanich mikroprocesorów 32-bitowych o wielkiej (w porównaniu do tych z poprzednich dekad) mocy przetwarzania i znacznych zasobach, można sobie zadać pytanie, na ile ta niezbyt pochlebna dla C++ opinia ma nadal pokrycie w faktach, a na ile jest utrzymującym się siłą inercji mitem.

C++: mity i fakty

Wyjątki

Wyjątki mają na celu obsługę sytuacji nienormalnych, zatem implementacje kładą nacisk na poprawienie efektywności programu w sytuacjach "normalnych", to jest wtedy, kiedy stan wyjątkowy nie występuje. Czas obsługi stanu wyjątkowego - jak to się często podkreśla w różnych źródłach - może być trudny do przewidzenia, a to z dwóch powodów: po pierwsze dlatego, że optymalizacja pod kątem sprawnej obsługi sytuacji normalnych jest często dokonywana kosztem większego obciążenia obsługi stanu wyjątkowego; po drugie, w grę wchodzi trudna do oceny liczba i czas wykonywania wszystkich destruktorów wywoływanych podczas obsługi stanu wyjątkowego.

Użycie wyjątków powoduje także powiększenie programu: kompilator musi do niego dodać zestaw tablic sterujących wykonaniem wspomnianych destruktorów oraz zawierających adresy procedur obsługi poszczególnych wyjątków. Z tego powodu wiele kompilatorów oferuje opcję wyłączenia tego wszystkiego: eliminuje to wszelkie koszty związane z wyjątkami (oraz, oczywiście, wszelkie związane z nimi korzyści).

Przykład użycia wyjątku:

#include <iostream>
using namespace std;
int silnia(int n) throw(const char*)
{
if (n<0)
throw "ujemny parametr";
if (n>0)
return n*silnia(n-1);
return 1;
}
int main()
{
try
{
int n = silnia(10);
cout << "silnia(10)=" << n;
}
catch (const char* s)
{
cout << "błąd silni: " << s << "n";
}
return 0;
}

Poniżej mamy odpowiednik w języku C:

#include <stdio.h>
#include <setjmp.h>
jmp _ buf Exception;
const char *ExceptionValue;
int silnia(int n)
{
if (n<0)
{
ExceptionValue = "ujemny parametr";
longjmp(Exception, 0);
}
if (n>0)
return n*silnia(n-1);
return 1;
}
int main()
{
if (setjmp(Exception)==0)
{
int n = silnia(10);
printf("silnia(10)=%d", n);
}
else
{
printf("błąd silni: %sn",
ExceptionValue);
}
return 0;
}

Jest tu kilka problemów: przede wszystkim, program w języku C używa zmiennych globalnych. Z tego powodu longjmp() może zostać omyłkowo wywołane jeszcze przed własnym zainicjowaniem albo już po powrocie z funkcji main(). Poza tym programista musi ręcznie wywołać wszystkie odpowiedniki destruktorów przed wywołaniem longjmp() - a jeśli tego przez zapomnienie nie zrobi, nie istnieje mechanizm, który pozwoliłby kompilatorowi wykryć tego typu pomyłkę.

We wszystkich poprzednich przykładach można było liczyć na to, że dla konkretnych konstrukcji C++ istnieją mniej lub bardziej praktyczne odpowiedniki w języku C. Ale symulacja wyjątków daje tyle okazji do popełnienia dodatkowych błędów oraz, ogólniej, tak komplikuje kod źródłowy, że trudno to uznać za odpowiednik szczególnie praktyczny.