Dzisiaj będzie o obiektach funkcyjnych (inaczej funktorach), ideą ich wprowadzenia jest “poprawienie” wad wskaźników na funkcje. Same obiekty funkcyjne zachowują się bardzo podobnie do tychże wskaźników jednak ich “implementacj” i zasada działania jest trochę inna. Do stworzenia funktora wykorzystamy przeciążony operator ():
struct funkcja
{
virtual void operator()() const
{
std::cout << "Funckja!" << std::endl;
}
};
W tym momencie mamy zadeklarowany obiekt funkcyjny. Aby z niego skorzystac musimy go zainstancjonować, kiedy posiadamy już instancje obiektu funkcyjnego, możemy z niego korzystać jak ze zwyklej funkcji bądź tez wskaźnika na nią:
// instancja obiektu funkcyjnego
funkcja f;
// wywołanie funkcji
f();
taki obiekt może zostać przekazany do innej funkcji np. w taki sposób:
void do_10_times(const funkcja &f)
{
for (int i = 0; i < 10; i ++)
{
f();
}
}
// ....
funkcja f;
// przekazanie istniejacego obiektu
do_10_times (f);
// utworzenie i wykorzystanie obiektu tymczasowego
do_10_times (funkcja());
Zaletą obiektów funkcyjnych jest też to, że mogą posiadać stan. Możemy przerobić nasz obiekt funkcyjny w taki sposób aby można było zmienić jego zachowanie:
struct funkcja
{
funkcja () : _value ("Funkcja!") {}
funkcja (const std::string &str) : _value (str) {}
virtual void operator()() const
{
std::cout << _value << std::endl;
}
private:
std::string _value;
};
// ...
// wywołanie domyślnego konstruktora
funkcja f1;
f1();
// zmiana zachowania obiektu funkcyjnego poprzez przekazanie parametru wkonstruktorze
funkcja f2("Hello world!");
f2();
Tutaj właściwie tylko wyobraźnia nas ogranicza. :) Kolejną istotną cechą funktorów, wynikającą z polimorifzmu w C++ jest to, że po takich obiektach możemy dziedziczyć, tworząc pewne klasy funkcji.. są to przecież zwykłe klasy, nic więcej.
struct funkcja2 : public funkcja
{
funkcja2 () : _second ("Funkcja2!") {}
funkcja2 (const std::string &a, const std::string &b) : funkcja (a), _second (b) {}
virtual void operator()() const
{
std::cout << _value << " " << _second << std::endl;
}
protected:
std::string _second;
};
//...
// wywołanie domyślnego konstruktora
funkcja2 f2;
f2();
funkcja2 f22("jakis", "tekst");
f22();
do_10_times (f22);
Na koniec zostawiłem sobie jeszcze jeden temat, mianowicie jak napisać generyczną funkcję przyjmującą jako parametr każdy obiekt funkcyjny bądź też wskaźnik na funkcję. Tutaj z odsieczą przychodzą nam szablony, rozwiązanie jest proste, przeróbmy zatem naszą funkcje do_10_times:
template <typename Fn>
void do_10_times(Fn f)
{
for (int i = 0; i < 10; i ++)
{
f();
}
}
void other_func()
{
std::cout << "Zwykła funkcja" << std::endl;
}
int main ()
{
do_10_times (funkcja());
do_10_times (funkcja2());
do_10_times (other_func);
return 0;
}
Biblioteka STL jak i boost dość często wykorzystuje ten mechanizm. I na koniec mała ciekawostka, jezeli skorzystamy z powyzszego mechanizmu to może okazać się, że obiekty funkcyjne będą bardziej wydajne, gdyz kompilator potrafi z-inline-owac nasz funktor (funkcja) do wyspecjalizowanego szablonu funkcji (do_10_times), omijając w ten sposób wywołanie. Ze wskaźnikami taka operacja optymalizacji byłaby niemożliwa.