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

Szablony

Szablony przypominają nieco makrodefinicje, które kompilator rozwija w całe klasy. Ponieważ jedno wywołanie szablonu umieszczone w kodzie programu może zostać rozwinięte w klasę, beztroskie korzystanie z szablonów może się szybko zemścić, powodując gigantyczny rozrost gotowego programu. Dzieje się tak zwłaszcza przy zastosowaniu starszych kompilatorów, które każde odwołanie do szablonu rozwijają w oddzielną klasę; ale nowsze kompilatory i linkery starają się wykrywać duplikaty i tworzyć najwyżej po jednej, unikalnej kopii każdego szablonu.

Szablony, jeśli zastosujemy je z głową, mogą zaoszczędzić programiście wiele wysiłku przy niewielkich stratach rozmiarów programu (lub w ogóle bez takich strat). W końcu przeważnie dużo łatwiej jest skorzystać z gotowego szablonu dostarczanego przez bibliotekę niż pisać własną złożoną klasę od początku.

Przykład poniżej ilustruje użycie szablonu:

template<typename T> class A
{
private:
T value;
public:
A(T);
T f();
};
template<typename T> A<T>::A(T initial)
{
value = initial;
}
template<typename T> T A<T>::f()
{
return value;
}
int main()
{
A<int> a(1);
a.f();
return 0;
}

Jak powiedziano powyżej, szablony działają podobnie jak makrodefinicje, można więc zamiast nich spróbować użyć makr preprocesora w języku C:

#define A(T)
struct A _ ##T
{
T value;
};

void konstruktor _ A _ ##T(struct A _ ##T *this,
T initial)
{
(this)->value = initial;
}

T A _ f _ ##T(struct A _ ##T *this)
{
return (this)->value;
}
A(int) /* tu makro rozwija się w "klasę" A _
int */
int main()
{
struct A _ int a;
konstruktor _ A _ int(&a, 1);
A_ f _ int(&a);
return 0;
}

Jak widać, rzecz sama w sobie jest jak najbardziej wykonalna, ale widać też, że jest to niezbyt praktyczne rozwiązanie w przypadku bardziej skomplikowanego kodu.