========================= Statischer Polymorphismus ========================= Bislang waren die Matrizen mit dem zugehörigen Speicher fest verbunden. Es ist vielfach aber auch sinnvoll, eine Matrix als Teil einer anderen Matrix zu betrachten wie z.B. bei Blockmatrizen. Diese Matrizen tragen keine Verantwortung für das Belegen oder Freigeben des zugehörigen Speichers, sie dienen nur als Betrachter eines Ausschnittes. In der Informatik gibt es hier das MVC-Pattern, wobei MVC für _model_, _viewer_ und _controller_ steht. Hier wäre die bisherige Matrix-Klasse das _model_ und der Betrachter eines Ausschnitts ein _viewer_ und, falls dieser auch Änderungen zulässt, ein _controller_. So könnte eine entsprechende Klasse aussehen. In ihrem Konstruktur erhält sie neben der Dimensionierung einen Zeiger auf das erste Element und die üblichen Inkremente: ---- CODE (type=cpp) ---------------------------------------------------------- template struct MatrixView { const std::size_t m; /* number of rows */ const std::size_t n; /* number of columns */ const std::size_t incRow; const std::size_t incCol; T* data; MatrixView(std::size_t m, std::size_t n, T* data, std::size_t incRow, std::size_t incCol) : m(m), n(n), data(data), incRow(incRow), incCol(incCol) { } const T& operator()(std::size_t i, std::size_t j) const { assert(i < m && j < n); return data[i*incRow + j*incCol]; } T& operator()(std::size_t i, std::size_t j) { assert(i < m && j < n); return data[i*incRow + j*incCol]; } }; ------------------------------------------------------------------------------- Im folgenden Beispiel entspricht `B` dem folgenden Ausschnitt aus `A`: ---- LATEX -------------------------------------------------------------------- \left( \begin{array}{c c c} a_{2,2} & a_{2,3} & a_{2,4} \\ a_{3,2} & a_{3,3} & a_{3,4} \\ a_{4,2} & a_{4,3} & a_{4,4} \end{array} \right) ------------------------------------------------------------------------------- ---- CODE (type=cpp) ---------------------------------------------------------- int main() { Matrix A(7, 8, StorageOrder::ColMajor); init_matrix(A); std::printf("A =\n"); print_matrix(A); MatrixView B(3, 3, &A(2, 2), A.incRow, A.incCol); std::printf("B =\n"); print_matrix(B); } ------------------------------------------------------------------------------- Leider funktioniert die bisherige `print_matrix`-Funktion nicht für die neu eingeführte `MatrixView`-Klasse: ---- SHELL (path=session9) -------------------------- g++ -Wall -std=gnu++11 -o matrix_class12 matrix_class12.cpp ----------------------------------------------------- Das Problem ist, dass die `print_matrix`-Funktion darauf besteht, dass ihr Matrix-Parameter eine Instantiierung einer Matrix-Klasse ist. Da C++ erst jedoch beim Einsetzen der Typparameter bei einer Instantiierung überprüft, ob das Resultat korrekt ist, können wir den Parameter allgemeiner formulieren: ---- CODE (type=cpp) ---------------------------------------------------------- template void print_matrix(const Matrix& A) { for (std::size_t i = 0; i < A.m; ++i) { std::printf(" "); for (std::size_t j = 0; j < A.n; ++j) { /* be careful here, printf is not polymorph */ std::printf(" %4.1lf", (double) A(i, j)); } std::printf("\n"); } } ------------------------------------------------------------------------------- Diese Template-Funktion akzeptiert jetzt zunächst einen beliebigen Datentyp für `Matrix`. Es wird nur erwartet, dass der resultierende Programmtext nach dem Einsetzen korrekt ist. Konkret bedeutet das hier, dass * A.m und A.n existieren und mit `std::size_t` vergleichbar sind und * A(i, j) zulässig ist, d.h. ein entsprechender Operator muss existieren und etwas zurückliefern, das in `double` konvertierbar ist. Dies sind die sogenannten Template-Abhängigkeiten. Bislang gibt es in C++ kein Sprachmittel, die geforderten Eigenschaften eines Typparameters in allgemeiner Form zu beschreiben, wenn von einer Konkretisierung des Typs (wie zuvor auf `Matrix`) abgesehen wird. Entsprechend ist `print_matrix` polymorph, da es für nicht für einen konkreten Datentyp verwendet werden kann, sondern für alle Datentypen, die die Template-Abhängigkeiten erfüllen. Da bereits der Übersetzer zur Übersetzzeit die richtige Ausprägung erzeugt und auswählt, wird hier vom statischen Polymorphismus gesprochen. Sei folgendes Testprogramm gegeben, das auch ein Element von `B` verändert: ---- CODE (type=cpp) ---------------------------------------------------------- int main() { Matrix A(7, 8, StorageOrder::ColMajor); MatrixView B(3, 3, &A(2, 2), A.incRow, A.incCol); init_matrix(A); B(1, 1) = 0; std::printf("A =\n"); print_matrix(A); std::printf("B =\n"); print_matrix(B); } ------------------------------------------------------------------------------- Dann sieht das Resultat so aus: ---- SHELL (path=session9) -------------------------------- g++ -Wall -std=gnu++11 -o matrix_class13 matrix_class13.cpp matrix_class13 ----------------------------------------------------------- Aufgaben ======== * Was passiert, wenn bei `print_matrix` statt eine der Matrix-Variablen ein völlig anderer Datentyp angegeben wird, etwa eine 1? * Kann ein `MatrixView`-Klasse sich auf eine andere `MatrixView`-Klasse beziehen? * Passen Sie die `init_matrix`-Funktion so an, dass sie ebenfalls für eine `MatrixView`-Instanz funktioniert. * Da zu einer `MatrixView`-Instanz nur wenige Komponenten gehören, lohnt es sich, einen Kopierkonstruktor anzubieten: ---- CODE (type=cpp) -------------------------------------------------------- MatrixView(const MatrixView& other) : /* ... */ { /* ... */ } ----------------------------------------------------------------------------- Sie können das testen, indem Sie eine weitere `MatrixView`-Klasse entsprechend initialisieren: ---- CODE (type=cpp) -------------------------------------------------------- MatrixView C = B; std::printf("C =\n"); print_matrix(C); ----------------------------------------------------------------------------- * Schreiben Sie eine Template-Funktion `create_view`, die eine beliebige Matrix erhält (also `Matrix` oder `MatrixView`) und einen Ausschnitt als `MatrixView` zurückliefert. Als Parameter sind die Indizes für das erste Element und die Zahl der Zeilen und Spalten anzugeben: So könnte ein Aufruf aussehen: ---- CODE (type=cpp) -------------------------------------------------------- MatrixView D(create_view(A, 1, 4, 3, 2)); std::printf("D =\n"); print_matrix(D); ----------------------------------------------------------------------------- Wenn Sie die Deklaration für `create_view` hinschreiben möchten, erhalten Sie aber das Problem, den Elementtyp benennen zu müssen, obwohl Sie nur den Matrix-Typ als Template-Parameter haben. Hierfür gibt es mehrere Lösungsmöglichkeiten in C++. Der klassische Weg führt über hilfreiche Typdeklarationen innerhalb von `struct Matrix` und `struct MatrixView`: ---- CODE (type=cpp) -------------------------------------------------------- template struct /* Matrix or MatrixView */ { /* ... */ typedef T Element; }; ----------------------------------------------------------------------------- Danach kann mit `Matrix::Element` auf den Elementtyp zurückgegriffen werden für beliebige Template-Parameter `Matrix`, die sich an diese Konvention halten. Es gibt da nur noch ein kleines Problem. Da die Grammatik von C++ mehrere Konflikte hat, muss die syntaktische Analyse bei C++ immer wissen, ob es sich bei einem Namen um einen Typ oder um etwas anderes handelt. Bei der syntaktischen Analyse von Templates ist aber der Template-Parameter noch nicht bekannt, so dass der Übersetzer nicht erraten kann, dass es sich bei `Matrix::Element` um einen Typen handelt. Deswegen muss der Übersetzer hier unterstützt werden, indem davor das Schlüsselwort `typename` verwendet wird, also `typename Matrix::Element` verwendet wird. Zusätzlich wäre noch ein Aufruf von `assert` sinnvoll, um sicherzugehen, dass die Teilmatrix sich auf einen Ausschnitt der Ausgangsmatrix beschränkt. * Welche Konstruktoren werden an welcher Stelle aufgerufen? Gehen Sie der Sache auf den Grund, indem Sie temporär die Konstruktoren mit Kontrollausgaben versehen. Vorlage ======= :import: session9/matrix_class13.cpp :navigate: up -> doc:index back -> doc:session9/page04 next -> doc:session9/page06