Forum: PC-Programmierung C++ forward declaraion


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Michael S. (michaelsmith)


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

von Andreas M. (amesser)


Lesenswert?

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.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


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.

von Niklas G. (erlkoenig) Benutzerseite


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
}

von Sven B. (scummos)


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.

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


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

von Sven B. (scummos)


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.

: Bearbeitet durch User
Beitrag #7632013 wurde vom Autor gelöscht.
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Sven B. (scummos)


Lesenswert?

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.

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


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
}

: Bearbeitet durch User
von Sven B. (scummos)


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

: Bearbeitet durch User
von Michael S. (michaelsmith)


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.

von Falk S. (falk_s831)


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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.