Forum: PC-Programmierung #include Problem Frage an c++ Profis :-)


von c++ (Gast)


Lesenswert?

Hallo zusammen,

ich habe zwei ganz einfache Headerdateien:

-> one.h von class One und
-> two.h von class Two


Wenn ich schreibe:

--------------------Datei one.h------------------------------

#pragma once

#include "two.h"

class One
{
public:

  Two * EinTwoObjekt;

  One();
  ~One();
};

--------------------Datei tow.h------------------------------

#pragma once

#include "one.h"

class Two
{
public:

  One * EinOwoObjekt;

  Two();
  ~Two();
};



Dann bekomme ich natürlich einen Fehler!

Warum weil #include "one.h" und #include "two.h",
selbst "one.h" oder "two.h" einbinden.

Also so z.B. :

--------------------Datei one.h------------------------------

#pragma once

// #include "two.h" -> wird durch den Präprozessor ersetzt

....
#include "one.h"    -> und hier taucht jetzt "one.h" in der "one.h" auf
....                -> und es kommt zu einem Fehler, oder ?

class Two
{
public:

  One * EinOwoObjekt;

  Two();
  ~Two();
};

class One
{
public:

  Two * EinTwoObjekt;

  One();
  ~One();
};

--------------------Datei one.h END ------------------------------




Wenn ich aber schreibe:

--------------------Datei one.h------------------------------

#pragma once

#include "two.h"

class Two;          // Neu dazugekommen

class One
{
public:

  Two * EinTwoObjekt;

  One();
  ~One();
};

--------------------Datei tow.h------------------------------

#pragma once

#include "one.h"

class One;          // Neu dazugekommen

class Two
{
public:

  One * EinOwoObjekt;

  Two();
  ~Two();
};

wird alles schön Kompiliert!

-> Warum ist das so?
-> Wie würdet Ihr das Problem lösen?

DANKE....

IDE VS2010

Gruß c++

von Sebastian Hepp (Gast)


Lesenswert?

Forwärtsdeklarationen nutzen ist die Lösung.

Zum Beispiel:
1
#ifndef HEADER_ONE
2
#define HEADER_ONE
3
4
class One;
5
6
#include "two.h"
7
8
class One {
9
  private:
10
    ...
11
};
12
13
#endif

von Fabian (Gast)


Lesenswert?

Du kannst am Ende die Includes sogar weglassen. Das Ganze nennt sich 
Forward Declaration. Das funktioniert, solange du nur mit Referenzen 
oder Pointern arbeitest. Man würde das genauso machen. Die Includes 
werden nur in den cpp-Dateien benötigt.

von B. S. (bestucki)


Lesenswert?

Header Dateien dürfen rekursiv eingebunden werden, jedoch sollte, wie 
bei allem das rekursiv arbeitet, eine Abbruchbedingung vorgesehen sein.

Das Problem ist aber (ich nehme an, dass #pragma once deine Include 
Guards sind), dass du in beiden Klassen die jeweils andere verwenden 
willst. Denke wie ein Compiler: Du kannst Two nicht kompilieren, weil 
One noch nicht bekannt ist und du kannst One nicht kompilieren, weil Two 
noch nicht bekannt ist.

c++ schrieb:
> class One;          // Neu dazugekommen
Das ist eine forward declaration.

c++ schrieb:
> -> Wie würdet Ihr das Problem lösen?
In diesem einfachen Fall mit einer forward declaration.

von c++ (Gast)


Lesenswert?

@ forward declaration -> danach habe ich gesucht....
Ich glaube so langsam kommt es an :-) DANKE

von c++ (Gast)


Lesenswert?

be s. schrieb:
> (ich nehme an, dass #pragma once deine Include
> Guards sind)

genau :-)

von Karl H. (kbuchegg)


Lesenswert?

c++ schrieb:

> -> Warum ist das so?

Weil der Compiler wissen will, ob das was du benutzt auch tatsächlich 
existiert oder ob du vielleicht einen Tippfehler gemacht hast.

> -> Wie würdet Ihr das Problem lösen?

Das das ganze FOrward Deklaration heisst, wurde ja schon genannt.
Mit einer Forward Deklaration
1
class One;
sagtst du dem Compiler also sinngemäss: es existiert eine Klasse namens 
'One' in meinem Programm.
Du verrätst hier keine Einzelheiten, also auch nicht wie diese Klasse 
aufgebaut ist, sondern du behauptest einfach nur: sie existiert.

Damit ist der Compiler zufrieden und weiss daher, dass du hier
1
 class Two
2
{
3
public:
4
5
One * EinOwoObjekt;
keinen Tippfehler gemacht hast, sondern dass es tatsächlich etwas gibt, 
was 'One' heisst. Und da er weiss, wie gross ein Pointer ist, benötigt 
er auch keinerlei Informationen über die innere Struktur dieses 'One'.

Etwas anderes wäre es, wenn du zum Beispiel über diesen Pointer eine 
Funktion des One-Objektes aufrufen willst
1
class One;
2
3
class Two
4
{
5
public:
6
7
  One * EinOwoObjekt;
8
9
  void doIt()   { EinOwoObjekt->machWas(); }
10
...

das klappt jetzt so auch nicht mehr. Denn selbstverständlich will der 
Compiler überprüfen, ob so ein One-Objekt auch wirklich über eine 
Member-Funktion namens 'machWas' verfügt. Das kann aber die 
Existenzbehauptung der Klasse One ein paar Zeilen darüber nicht leisten. 
Daraus ist ja nichts über den inneren Aufbau dieser Klasse ableitbar. 
Dazu muss der Compiler dann schon den kompletten Aufbau der Klasse vor 
der Verwendung im Funktionsaufruf gesehen haben.

: Bearbeitet durch User
von c++ (Gast)


Lesenswert?

Karl H. schrieb:
> class One;
>
> class Two
> {
> public:
>
>   One * EinOwoObjekt;
>
>   void doIt()   { EinOwoObjekt->machWas(); }

Jetzt ist bei mir endgültig der Groschen gefallen juhu :-)

---------------- one.h ---------------------------------------

#pragma once

#include "two.h"

class Two;

class One
{
public:

  Two * EinTwoObjekt;
  void doIt() { EinTwoObjekt->getI(); }
  One();
  ~One();
};

Das geht :-)

von c++ (Gast)


Lesenswert?

c++ schrieb:
> #include "one.h"    -> und hier taucht jetzt "one.h" in der "one.h" auf
> ....                -> und es kommt zu einem Fehler, oder ?

..... und das "one.h" in "one.h" auftaucht stimmt ja sowas von nicht :-)
jetzt ist es mir klar DANKE

von Karl H. (kbuchegg)


Lesenswert?

c++ schrieb:

>
> ---------------- one.h ---------------------------------------
>
> #pragma once
>
> #include "two.h"
>
> class Two;
>
> class One
> {
> public:
>
>   Two * EinTwoObjekt;
>   void doIt() { EinTwoObjekt->getI(); }
>   One();
>   ~One();
> };
>
> Das geht :-)

An dieser Stelle ist dann die Forward Deklaration unsinnig. Denn 
entweder du inkludierst two.h welches die komplette Klassendefinition 
von Two einbringt, oder du machst eine Forward-Deklaration. Der Sinn 
einer Forward Deklaration besteht ja gerade darin, dass man (für 
eingeschränkte Zwecke) die vollständige Deklaration nicht benötigt.

Hier benötigst du die vollstänsige Deklaration, denn anders wäre der 
Aufruf der Member Funktion nicht zu machen. Also muss der #include, über 
den sie bereit gestellt wird drinnen bleiben und die Forward-Deklaration 
ist an dieser Stelle unnötig (weil ja ohnehin schon vorher die 
vollständige Deklaration von Two eingebunden wurde). Denn das wäre ja 
ein bisschen sinnfrei, wenn du vorher den genauen Aufbau von irgendetwas 
herzeigen würdest nur um dann nachher nochmal (quasi "bekräftigend") 
auszudrücken "Jawohl, das existiert." Es ist in diesem Sinne nicht 
falsch, aber es ist sinnfrei.

: Bearbeitet durch User
von c++ (Gast)


Lesenswert?

Karl H. schrieb:
> Hier benötigst du die vollstänsige Deklaration, denn anders wäre der
> Aufruf der Member Funktion nicht zu machen. Also muss der #include, über
> den sie bereit gestellt wird drinnen bleiben und die Forward-Deklaration
> ist an dieser Stelle unnötig (weil ja ohnehin schon vorher die
> vollständige Deklaration von Two eingebunden wurde)

Äh, stimmt :-)

von c++ (Gast)


Lesenswert?

Aber Probleme würde es geben wenn ich in beiden, also:

in two.h

  int getI(void) { return i; }
  void doIt() { EinOwoObjekt->getI();


und


in one.h

  int getI(void) { return i; }
  void doIt() { EinTwoObjekt->getI();

schreibe. Weil beide auf die vollständige komplette Klassendefinition 
zugreifen wollen , oder ?

von B. S. (bestucki)


Lesenswert?

c++ schrieb:
> Weil beide auf die vollständige komplette Klassendefinition
> zugreifen wollen , oder ?

Ja. Du musst die Funktionen in eine separate Sourcedatei packen, in dem 
du die entsprechenden Header einbindest.

von c++ (Gast)


Lesenswert?

be s. schrieb:
> Ja. Du musst die Funktionen in eine separate Sourcedatei packen, in dem
> du die entsprechenden Header einbindest

Alles klar vielen Dank -> gerade an Karl Heinz und be stucki und alle 
anderen natürlich. Jetzt mache Feierabend :-)

von Karl H. (kbuchegg)


Lesenswert?

be s. schrieb:
> c++ schrieb:
>> Weil beide auf die vollständige komplette Klassendefinition
>> zugreifen wollen , oder ?
>
> Ja. Du musst die Funktionen in eine separate Sourcedatei packen, in dem
> du die entsprechenden Header einbindest.

Es gibt eine Möglichkeit auch dieses Problem zu umgehen. Niemand sagt, 
dass die inline Funktionsrümpfe in der Klassendeklaration stehen müssen. 
Mit einer Mischung aus vorhergehender Forward-Deklaration und einem 
#include nach der Klassendeklaration lässt sich auch das in den Griff 
kriegen.
1
#ifndef ONE_H
2
#define ONE_H
3
4
class Two;
5
6
class One
7
{
8
  ...
9
10
  void doIt();
11
  void machWas();
12
13
  Two* pTwo;
14
};
15
16
include "two.h"
17
18
inline void One::DoIt()
19
{
20
  pTwo->machWas();
21
}
22
23
#endif
1
#ifndef TWO_H
2
#define TWO_H
3
4
class One;
5
6
class Two
7
{
8
  ...
9
10
  void doIt();
11
  void machWas();
12
13
  One* pOne;
14
};
15
16
include "one.h"
17
18
inline void Two::DoIt()
19
{
20
  pOne->machWas();
21
}
22
23
#endif

Aber erst mal soll er die ganz normale 'Straight Forward' Lösung in den 
Griff kriegen. Derartige enge Kopplungen zwischen Klassen sind eher 
selten und man sollte dann auch ins Auge fassen, ob man hier nicht 
überhaupt einen Designfehler hat.

von Matthias H. (hallamen)


Lesenswert?

Eine andere (jedoch zugegebenermassen nicht sehr elegante) Möglichkeit 
ist, beide Klassen als template du deklarieren. Die Implementierungen 
der Funktionen müssen dadurch erst bei der Instanziierung der 
template-Klassen bekannt sein.

1
template<class S> class B_;
2
3
template<class T>
4
struct A_ {
5
  B_<T> b;
6
  A_() { b.Foo(); }
7
};
8
9
template<class S>
10
struct B_ {
11
    void Foo() {}
12
};
13
14
typedef A_<int> A;
15
typedef B_<int> B;
16
17
void bar() {
18
    A a;
19
    B b;
20
}

von c++ (Gast)


Lesenswert?

Karl H. schrieb:
> Derartige enge Kopplungen zwischen Klassen sind eher
> selten und man sollte dann auch ins Auge fassen, ob man hier nicht
> überhaupt einen Designfehler hat.

Das stimmt natürlich :-)

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.