Hallo,
ich habe eine Art Huhn und Ei Problem, daher möchte eine Klasse voraus
deklarieren.
Ich wollte wissen wie ich die class MyClass voraus deklarieren kann
so dass die class IUseMyClassAsBase mit der Vererbung dieser keine
Probleme hat. Soll ich die Basisklassen bei der voraus Deklarierung
immer mit angeben ?
Bsp.
internal stellt ein namespace dar mit den Base Klassen,
meine class MyClass befindet sich aber ausserhalb dieses internal
namespaces. class MyClass is eine 'nested class' und befindet sich in
der class MainClass
1
namespace internal
2
{
3
...
4
class BaseClass<>...
5
...
6
class BaseClassRef<> ...
7
...
8
} // namespace internal
9
10
class MainClass
11
{
12
13
//forward declaration -> *MACHT DAS SINN* ?
14
template<typename T>
15
class MyClass : public internal::BaseClass<internal::BaseClassRef<T>>;
16
17
class IUseMyClassAsBase : public MyClass<>
18
{
19
...
20
{
21
...
22
template<typename T>
23
class MyClass : public internal::BaseClass<internal::BaseClassRef<T>>
24
{
25
...
26
// here I access IUseMyClassAsBase related functionality
Das dürfte nicht funktionieren. Wenn man von einer Klasse was ableiten
will, dann muss die gesamte Klassendefinition dafür verfügbar sein und
nicht nur deklariert sein.
Vielleicht erklärst Du uns einfach, welches Problem Du mit dem Konstrukt
lösen möchtest. Grundsätzlich braucht der Compiler den complete type der
Basis Klassen. Für das Verständnis Deines Problems, kannst Du die
namespaces sicher erst mal weg lassen. MainClass::MyClass ist ein
Template, dass keine default Parameter hat, damit fehlt bei `MyClass<>`
ein Parameter.
// here I access IUseMyClassAsBase related functionality
10
}
11
12
classIUseMyClassAsBase:publicMyClass<>
13
{
14
...
15
}
16
}
Die Implementationen der Funktionen von MyClass müssen dann ggf. ganz
am Ende kommen, nachdem IUseMyClassAsBase vollständig definiert wurde,
falls du dort Instanzen von IUseMyClassAsBase anlegen oder Funktionen
davon aufrufen möchtest.
PS: Verschachtelte templates würde ich sparsam einsetzen, die helfen der
Lesbarkeit nicht besonders. Man kann aber Aliase gewinnbringend
einsetzen:
Dein Problem ist leider ziemlich abstrakt formuliert und daher schwer
verständlich. Es hilft dieser Art von Frage ungemein, einen Schritt
zurückzutreten und das Ziel zu formulieren, was erreicht werden soll,
anstatt 95% der Lösung mit einem Detail-Roadblock zu posten. Der ist
nämlich oft nicht lösbar und es muss stattdessen ein komplett anderer
Ansatz gewählt werden, wobei man dir aber kaum helfen kann, weil man
erst reverse-engineeren muss was du eigentlich tust.
Die "Maskierung" von Klassennamen zu "MyClass" und "Foo" und "X" ist
auch extrem hinderlich bei einer Diskussion weil sie es unmöglich macht
beim Formulieren einer Lösung die tatsächlichen Requirements deiner
Anwendung von over-engineertem Nonsens zu trennen.
Davon abgesehen suchst du evtl. folgendes Pattern?
1
template<typename T>
2
class IUseMyClassAsBase : public T { ... };
und dann MyClass für T einsetzen. Damit kannst du den Code hinschreiben,
aber er wird erst später vom Compiler gelesen, wenn T eingesetzt wird.
Forward Declarations helfen dir hier keinesfalls, damit kannst du
eigentlich nur Pointer und Referenzen auf den unbekannten Typ machen und
das war's.
Sven B. schrieb:> Forward Declarations helfen dir hier keinesfalls, damit kannst du> eigentlich nur Pointer und Referenzen auf den unbekannten Typ machen und> das war's.
Doch, in der korrekten Reihenfolge schon. Alles andere außer Pointer &
Referenzen macht sowieso keinen Sinn - also z.B. zirkulär ableiten oder
Instanzen anlegen - weil das eine unendlich große Datenstruktur bedeuten
würde...
Niklas G. schrieb:> Alles andere außer Pointer &> Referenzen macht sowieso keinen Sinn - also z.B. zirkulär ableiten oder> Instanzen anlegen - weil das eine unendlich große Datenstruktur bedeuten> würde...
Einfaches Gegenbeispiel: eine Baumstruktur mit 2 Typen von Knoten, die
immer nur den jeweils anderen Typ als Kind haben können:
1
class Node1 {
2
std::optional<Node2> child;
3
}
4
5
class Node2 {
6
std::optional<Node1> child;
7
}
Geht in C++ in dieser Form nicht, macht aber absolut Sinn. Im
Allgemeinen erlaubt die Sprache mit den Forward Declarations eben nur
eine sehr kleine Klasse solcher Rekursionen, mit sehr trivialem statisch
analysierbarem Verhalten. Es gibt aber noch viel mehr solche Strukturen,
die nicht möglich sind, und zwar auch solche, bei denen schon statisch
prüfbar wäre dass das immer geht, der Compiler das aber nicht kann.
Sven B. schrieb:> macht aber absolut Sinn.
Nur wenn beide Klassen leer sind?! std::optional enthält aber schon ein
Bool, ist also mindestens 1 Byte groß. Dann wäre:
sizeof(Node1)=sizeof(Node2)+1
und sizeof(Node2)=sizeof(Node1)+1
Das ist somit rekursiv und unendlich.
Ok, stimmt, dieses Beispiel ist tatsächlich Quatsch. Dann nimm zwei
Typen mit Member-Funktionen, die Instanzen des jeweils anderen Typs by
value als Argument nehmen.
Sven B. schrieb:> Dann nimm zwei> Typen mit Member-Funktionen, die Instanzen des jeweils anderen Typs by> value als Argument nehmen.
Das ist kein Problem, wenn die Funktionen nach der Definition beider
Klassen definiert sind:
Niklas G. schrieb:> Das ist kein Problem, wenn die Funktionen nach der Definition beider> Klassen definiert sind.
Ah, spannend, war mir nicht klar dass das geht für die Deklaration.
> Nur wenn beide Klassen leer sind?! std::optional enthält aber schon ein> Bool, ist also mindestens 1 Byte groß.
Geht übrigens auch sonst nicht, weil das sonst nicht eindeutig
adressierbar ist, siehe
https://en.cppreference.com/w/cpp/language/attributes/no_unique_address
Hallo, ok, wie ich verstanden habe muss ich einen anderen Weg suchen.
Zumindest die Definition wenigstens einer Klasse muss gegeben/bekannt
sein.
Ich ginge von aus, dass einfaches Mitteilen dem Compiler, dass die
Definition später folgt, für beide Klassen ausreichen wäre.
Danke für die Hinweise.
Michael S. schrieb:> Hallo, ok, wie ich verstanden habe muss ich einen anderen Weg suchen.> Zumindest die Definition wenigstens einer Klasse muss gegeben/bekannt> sein.
Wobei dein Nutzungsfall auch so leicht fraglich ist, was du damit
bezwecken willst. Grundsätzlich macht es nur Sinn aus C++-Sicht, zu
fordern, dass die Basisklasse klar bekannt sein muss. Denn Vererbung
garantiert ja, dass die abgeleitete Klasse eine Spezialisierung der
Basisklasse ist (is-a-relationship). Wenn du die Basisklasse p-impeln
könntest (vorwärtsdeklarieren), macht das ja so keinen Spaß.
Die Lösung für das Problem was du suchst, ist P-IMPL-Pattern in
Verbindung mit dem Favor-Composition-Over-Inheritance-Prinzip.