F
Noch eine Anmerkung am Rande: Ein weiterer Vorteil der Trennung in .hpp/.inl ist mir gerade erst wieder beim Programmieren aufgefallen (eher fortgeschrittene Problematik):
Folgendes Beispielszenario mit statischer Vererbung via CRTP ist normalerweise nicht immer ganz einfach sauber hinzubekommen. Bei getrennten .hpp/.inl löst es sich jedoch nahezu ganz von alleine in Wohlgefallen auf:
// MatrixExpression.hpp
#pragma once
...
template <typename E>
struct MatrixExpression
{
// Funktion soll ein Column (aus Column.hpp) zurückgeben. Bei dieser
// reinen Deklaration wird zwar noch kein vollständiger Typ und daher
// auch kein #include benötigt, das auto spart hier allerdings eine
// Forward Declaration.
auto column(std::size_t j) const;
};
...
#include <MatrixExpression.inl>
// Column.hpp
#pragma once
...
// Column ist zu diesem Zeitpunkt noch nicht deklariert. Wenn nun
// MatrixExpression jedoch einen vollständigen Column-Typen benötigt,
// wie z.B. in der Definition der Member-Funktion column(), wird es
// hier knallen.
#include "MatrixExpression.hpp"
...
// Column erbt von MatrixExpression, es wird daher der vollständige Typ
// benötigt und man kommt um das #include oben nicht herum.
template <typename E>
class Column : public MatrixExpression<Column<E>>
{
...
};
// MatrixExpression.inl
#pragma once
...
// Hier zeigt sich der praktische Effekt dieser Trennung: Normalerweise
// würde Column.hpp auch MatrixExpression.hpp inklusive der nachfolgenden
// Funktion column() einbinden, *bevor* Column überhaupt deklariert wurde,
// und somit zu einer Compiler-Fehlermeldung führen.
//
// Stattdessen passiert aber nun folgendes:
//
// 1. User-Code bindet MatrixExpression.hpp ein.
// 2. MatrixExpression.hpp deklariert MatrixExpression und bindet
// MatrixExpression.inl ein.
// 3. MatrixExpression.inl bindet "Column.hpp" ein.
// 4. Column.hpp versucht, MatrixExpression.hpp einzubinden, was aber
// ignoriert wird, da Include-Guard greift. Column läuft aber dennoch
// nicht auf Fehler, da MatrixExpression bereits in Schritt 2 deklariert
// wurde. Column ist nach diesem Schritt vollständig deklariert.
// 5. MatrixExpression.inl definiert column(), welches letztendlich das
// vollständige Column benötigt, was wegen Schritt 4 dann auch
// fehlerfrei kompiliert.
#include "Column.hpp"
...
// Column muss hier ein vollständiger Typ sein, da ein Objekt diesen Typs
// konstruiert wird.
template <typename E>
auto MatrixExpression<E>::column(std::size_t j) const
{
return Column{ static_cast<const E&>(*this), j };
}
Heh... eigentlich wollte ich nicht so viel Text produzieren. Dachte mir nur gerade "Hey, das Thema hatten wir doch gestern" und wollte eigentlich nur einen kurzen Kommentar dazu absetzen. Sorry, bei manchen Dingen tue ich mich extrem schwer mit kurzen und bündigen Beschreibungen