#include Problem Frage an c++ Profis :-)

Gast #4373277
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++
Gast #4373289
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.
#4373291
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.
#4373315
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.
Gast #4373332
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 :-)
#4373349
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.
Gast #4373353
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 :-)
Gast #4373368
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 ?
#4373588
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.
#4373617
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
}

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