C++ forward declaraion

OP #7631841
Lesenswert?

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
27
    }
28
}

VG Michael

(Firma: Torrox.de) #7631875
Lesenswert?

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.

Persönliche Seite #7631876
Lesenswert?

Eher so rum:

1
class MainClass
2
{
3
    class IUseMyClassAsBase;
4

5
    template<typename T>
6
    class MyClass : public internal::BaseClass<internal::BaseClassRef<T>>
7
    {
8
    ...
9
    // here I access IUseMyClassAsBase related functionality
10
    }
11

12
    class IUseMyClassAsBase : public MyClass<>
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:

1
class IUseMyClassAsBase;
2

3
template<typename T>
4
class MainClass_MyClass : public internal::BaseClass<internal::BaseClassRef<T>>
5
{
6
...
7
// here I access IUseMyClassAsBase related functionality
8
}
9

10
class MainClass_IUseMyClassAsBase : public MyClass<>
11
{
12
 ...
13
}
14

15
class MainClass
16
{
17
  template<typename T>
18
  using MainClass = MainClass_MyClass<T>;
19
  
20
  using IUseMyClassAsBase = MainClass_IUseMyClassAsBase;
21
}
#7631898
Lesenswert?

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.

Persönliche Seite #7631936
Lesenswert?

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...

#7632010
Lesenswert?

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.

Beitrag #7632013 wurde von einem Moderator gelöscht.
Persönliche Seite #7632021
Lesenswert?

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:

1
class B;
2
 
3
class A {
4
  int x;
5
  void test (B b);
6
};
7
 
8
class B {
9
  short y;
10
  void test (A a);
11
};
12
 
13
void A::test (B b) {
14
}
15
 
16
void B::test (A a) {
17
}
#7632022
Lesenswert?

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

OP #7632593
Lesenswert?

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.

#7633656
Lesenswert?

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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren