Funktionsobjekte

Es lohnt sich, die neue Fassung von initGeMatrix noch einmal genau anzusehen:

template <typename T, typename Index, typename InitValue>
void
initGeMatrix(Index m, Index n, T *A, Index incRowA, Index incColA,
             InitValue initValue)
{
    for (Index j=0; j<n; ++j) {
        for (Index i=0; i<m; ++i) {
            A[i*incRowA+j*incColA] = initValue(i, j, m, n);
        }
    }
}

Es ist offensichtlich, dass ein Funktionstyp mit vier Parametern bei InitValue übergeben werden kann. Es ist aber darüber hinaus auch möglich, Objekte einer Klasse mit einem entsprechenden Funktions-Operator zu übergeben. Das war zuvor bei der sehr expliziten Typspezifikation für initValue als Funktionszeiger nicht möglich.

Objekte mit einem Funktionsoperator werden in C++ Funktionsobjekte genannt.

Wann sind Funktionsobjekte im Vergleich zu einfachen Funktionen sinnvoll? Das Problem ist, dass einfache Funktionen nur ihre Parameter auswerten können (und ggf. globale Datenstrukturen). Häufig besteht aber Anlass, eine Funktion in einem Kontext aufzurufen, d.h. unter Verwendung von Variablen, die nicht als Parameter übergeben werden und die auch nicht global gehalten werden sollen. Letzteres könnte uns auch später ein ernstes Problem in Bezug auf die thread safety geben.

Wie wichtig der Kontext sein kann, wird offenbar, wenn wir folgende Fassung von initGeMatrix auf Basis unserer neuen Funktion initGeMatrix umsetzen möchten:

template <typename T, typename Index>
void
initGeMatrix(Index m, Index n, T *A, Index incRowA, Index incColA)
{
    std::random_device                random;
    std::mt19937                      mt(random());
    std::uniform_real_distribution<T> uniform(-100, 100);

    for (Index j=0; j<n; ++j) {
        for (Index i=0; i<m; ++i) {
            A[i*incRowA+j*incColA] = uniform(mt);
        }
    }
}

Das Problem ist hier, dass ein Pseudo-Zufallszahlengenerator mt angelegt und danach kontinuierlich verwendet wird. Dieser ist hier lokal und sollte keinesfalls global werden. Wie lässt sich das bei der Lösung mit dem initValue-Parameter erreichen? Nun, wenn wir statt einer einfachen Funktion ein Funktionsobjekt verwenden, dann kann dieses Objekt beliebig viel Status verwalten wie beispielsweise die Variablen mt und uniform:

template<typename T, typename Index>
struct RandomValues {
   std::mt19937 mt;
   std::uniform_real_distribution<T> uniform;

   RandomValues() :
      mt(std::random_device()()), uniform(-100, 100) {
   }
   T operator()(Index i, Index j, Index m, Index n) {
      return uniform(mt);
   }
};

So könnte dann eine Initialisierung aussehen:

initGeMatrix(A, RandomValues<double, std::size_t>());

Aufgaben