Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

[C++] Pointer assignment met forward class declaration

Pagina: 1
Acties:

  • sanderarts
  • Registratie: Oktober 2007
  • Laatst online: 30-10-2007
In mijn code heb ik een circular dependency, class A heeft class B nodig en andersom. Ze refereren naar elkaar via pointers.
Ik heb de code opgedeeld in header files en source. Waarbij ik in de headers al de pointers naar de andere class definieer.
Het probleem is het volgende, als ik vanuit class A een self-reference mee geef aan class B, dan ontstaat er een segmentation fault als ik de reeds bestaande pointer van class B probeer te wijzigen.

C++: a.h
1
2
3
4
5
6
7
8
9
class B;

class A
{
    private:
        B * b;
    public:
        void start();
};



C++: b.h
1
2
3
4
5
6
7
8
9
class A;

class B
{
    private:
        A * a;
    public:
        void setA(A*);
};


C++: a.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "a.h"
#include "b.h"

void A::start()
{
    A * ptr;
    ptr = this;
    b->setA(ptr);
};

int main()
{
    A a;
    a.start();
    return 0;
};


C++: b.cpp
1
2
3
4
5
6
7
8
9
#include "b.h"
#include "a.h"

using namespace std;

void B::setA(A* newA)
{
    this->a = newA;
};


Hoe kan ik er voor zorgen dat class B een pointer naar de instantie van class A krijgt?

  • user109731
  • Registratie: Maart 2004
  • Niet online
Je member variable b in class A is niet geinitialiseerd. setA erop aanroepen geeft daarom een segmentation fault :)

[ Voor 32% gewijzigd door user109731 op 29-10-2007 18:01 ]


  • Jaap-Jan
  • Registratie: Februari 2001
  • Laatst online: 19:03
class A moet een functie setB() hebben en class B een functie setA(). Waarom je hier trouwens een segmentation fault krijgt, is simpel:

in A.h staat
C++:
1
2
private:
    B * b;


en in A.cpp doe je eigenlijk (maar jij doet dat met een omweg):
C++:
1
b->setA(this);
Maar die pointer naar b wijst op dat moment naar een willekeurig stuk geheugen, want hij is niet geïnitialiseerd op het moment dat je die methode aanroept.

Een mogelijkheid is om je mainloop er zo uit te laten zien:
C++:
1
2
3
4
5
6
int main() {
    A a;
    B b;
    a.setB(&b);
    b.setA(&a);
}

[ Voor 3% gewijzigd door Jaap-Jan op 29-10-2007 18:22 ]

| Last.fm | "Mr Bent liked counting. You could trust numbers, except perhaps for pi, but he was working on that in his spare time and it was bound to give in sooner or later." -Terry Pratchett


  • blackangel
  • Registratie: April 2002
  • Laatst online: 27-11 13:21
Jaap-Jan schreef op maandag 29 oktober 2007 @ 18:05:
Een mogelijkheid is om je mainloop er zo uit te laten zien:
C++:
1
2
3
4
5
6
int main() {
    A a;
    B b;
    a->setB(b);
    b->setA(a);
}
Het idee klopt helemaal, het gaat alleen hard fout :P In praktijk moet je dan a.setB(&b) doen. object->functie() is gelijk aan (*object).functie(), en aangezien a en b zijn geen pointer zijn, is een eenvoudige '.' voldoende :)

Verder is het wel zo handig om het adres van b mee te geven (in plaats van een kopie van b), dus &b om het adres van b mee te geven. Anders krijg je daar ook nog een errortje op :P

  • Jaap-Jan
  • Registratie: Februari 2001
  • Laatst online: 19:03
blackangel schreef op maandag 29 oktober 2007 @ 18:13:
[...]

Het idee klopt helemaal, het gaat alleen hard fout :P In praktijk moet je dan a.setB(&b) doen. object->functie() is gelijk aan (*object).functie(), en aangezien a en b zijn geen pointer zijn, is een eenvoudige '.' voldoende :)

Verder is het wel zo handig om het adres van b mee te geven (in plaats van een kopie van b), dus &b om het adres van b mee te geven. Anders krijg je daar ook nog een errortje op :P
Hmm, daar was ik zelf ook achter gekomen, toen ik het wilde proberen, ik zat iets teveel met Java in mijn hoofd :o.

Nu ik nog eens lees maak je zelf ook een fout:
Verder is het wel zo handig om het adres van b mee te geven (in plaats van een kopie van b), dus &b om het adres van b mee te geven. Anders krijg je daar ook nog een errortje op :P
Je geeft geen kopie van A mee door setA(a) te doen, je moet dat doen door de signature van de functie aan te passen van void setA(A*) naar void setA(A). Die andere manier is gewoon een fout omdat de functie een pointer verwacht, maar een reference krijgt :P.

[ Voor 30% gewijzigd door Jaap-Jan op 29-10-2007 18:46 ]

| Last.fm | "Mr Bent liked counting. You could trust numbers, except perhaps for pi, but he was working on that in his spare time and it was bound to give in sooner or later." -Terry Pratchett


  • blackangel
  • Registratie: April 2002
  • Laatst online: 27-11 13:21
offtopic:
Beetje offtopic, maargoed. Beter dan misverstanden :)


Mwa, ook dat klopt niet helemaal.

Gebruik maken van & om het adres van een object op te vragen:
C++:
1
2
3
4
5
6
7
8
9
void resetInt(int *r) {
  *r = 0;
}

void main(void) {
  int i=1;
  resetInt(&i);
  cout << "i=" << i;
}
Geeft:
i=0

Gebruik maken van & als reference
C++:
1
2
3
4
5
6
7
8
9
void resetInt(int &r) {
  r = 0;
}

void main(void) {
  int i=1;
  resetInt(i);
  cout << "i=" << i;
}
Geeft
i=0


Bij het gebruik maken van referenties hoef je alleen bij de declaratie een & mee te geven. Voor de rest kun je het als normaal gedeclareerd object behandelen. Veel mensen vinden dat handig, anderzeids heb ik meer dan een jaar geprogrammeerd in C/C++ zonder ooit van referenties gehoord te hebben (meer C dan C++ ;) )
Je geeft geen kopie van A mee door setA(a) te doen, je moet dat doen door de signature van de functie aan te passen van void setA(A*) naar void setA(A).
Ja en nee. De functieaanroep setA(a) geeft (normaal gesproken) wel een kopie mee, alleen zou de declaratie dan fout zijn. Een van de twee had inderdaad aangepast moeten worden :)

Daarnaast klopt deze zin totaal niet:
Die andere manier is gewoon een fout omdat de functie een pointer verwacht, maar een reference krijgt :P.
De functie verwacht wel een pointer, maar krijgt geen reference. In C++ moet je expliciet declareren of je een reference wil, anders krijg je gewoon een kopie van het object.

Basicly dus:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void b::setA(A a); 
void a::setB(B b);
/* kopie. als je a.setB(this) doet, wordt dat niet in 
de de a in de mainfunctie gedaan. Het zijn twee
verschillende objecten, met bij het starten van setA
dezelfde waarden */

void b::setA(A *a);
void a::setB(B *b);
/* pointer. Kun je alles mee doen, behalve hem 
netjes verwijderen. Binnen de 
functie kun je bijvoorbeeld doen a->setB(this); */

void b::setA(A &a); 
void a::setB(B &b);
/*reference. Kun je alles mee doen behalve verwijderen
en opnieuw declareren. Voordeel is dat je hem kunt 
behandelen als object, dus aanroepen via a.setB(*this) */


Referenties zijn mooi, veilig, maar ik geef de voorkeur aan pointers eerlijk gezegd :)

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 20:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

blackangel schreef op maandag 29 oktober 2007 @ 19:32:
/* pointer. Kun je alles mee doen, behalve hem
netjes verwijderen.
Waarom zou je de pointer (of eigenlijk: het object waar de pointer naar wijst) niet kunnen verwijderen? 't Is natuurlijk maar net hoe je de verantwoordelijkheden hebt gedefinieerd, maar vanuit het perspectief van de taal is er niets mis mee om een delete a; te doen. Maar goed, nog beter gebruik je gewoon smart pointers.
Referenties zijn mooi, veilig, maar ik geef de voorkeur aan pointers eerlijk gezegd :)
En dat terwijl het C++ paradigima heel erg op referenties leunt ;). Ik gebruik pointers eigenlijk alleen als ze moeten kunnen veranderen of 0 kunnen zijn.

[ Voor 6% gewijzigd door .oisyn op 29-10-2007 20:02 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Jaap-Jan
  • Registratie: Februari 2001
  • Laatst online: 19:03
blackangel schreef op maandag 29 oktober 2007 @ 19:32:
offtopic:
Beetje offtopic, maargoed. Beter dan misverstanden :)
Inderdaad :).
[...]

Ja en nee. De functieaanroep setA(a) geeft (normaal gesproken) wel een kopie mee, alleen zou de declaratie dan fout zijn. Een van de twee had inderdaad aangepast moeten worden :)
Waarvan het aanpassen van de declaratie nogal raar is en vaak nieteens mogelijk, bijvoorbeeld als je tegen een API van een derde partij programmeert. Daarom is het een beetje raar om te zeggen dat 'de declaratie fout zou zijn', imho.
Daarnaast klopt deze zin totaal niet:

[...]

De functie verwacht wel een pointer, maar krijgt geen reference. In C++ moet je expliciet declareren of je een reference wil, anders krijg je gewoon een kopie van het object.
Dat was inderdaad een foute stelling, gebaseerd op de error die de compiler uitspuugde:
code:
1
error: no matching function for call to 'B::setA(A&)'

| Last.fm | "Mr Bent liked counting. You could trust numbers, except perhaps for pi, but he was working on that in his spare time and it was bound to give in sooner or later." -Terry Pratchett


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
.oisyn schreef op maandag 29 oktober 2007 @ 20:01:
Ik gebruik pointers eigenlijk alleen als ze moeten kunnen veranderen of 0 kunnen zijn.
Dan mag jij een cyclische referentie voor de TS maken met twee references :)

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 20:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

Goed interpreteren: als een referentie niet meteen geïnitialiseerd kan worden impliceert dat dat ze moeten kunnen veranderen, en volgens mijn eigen logica moet ik in dat geval dus iig 1 pointer gebruiken :)

Maar om toch antwoord te geven op je vraag:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct A;
struct B;

struct A
{
    A();
    B & b;
};

struct B
{
    B(A &);
    A & a;
};

A::A() : b(*new B(*this))
{
}

B::B(A & _a) : a(_a)
{
}

Potentieel gevaarlijk natuurlijk, omdat A nog niet volledig is geconstrueerd in B::B()
(en bovendien leakt dit, maar dat terzijde)

[ Voor 49% gewijzigd door .oisyn op 29-10-2007 21:59 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • blackangel
  • Registratie: April 2002
  • Laatst online: 27-11 13:21
.oisyn schreef op maandag 29 oktober 2007 @ 20:01:
[...]

Waarom zou je de pointer (of eigenlijk: het object waar de pointer naar wijst) niet kunnen verwijderen? 't Is natuurlijk maar net hoe je de verantwoordelijkheden hebt gedefinieerd, maar vanuit het perspectief van de taal is er niets mis mee om een delete a; te doen. Maar goed, nog beter gebruik je gewoon smart pointers.
Ik zeg ook niet dat je hem niet kunt verwijderen, alleen dat het niet netjes kan. Ik vind constructies waarin je in een andere functie een pointer naar een ongealloceerd stuk geheugen over houdt niet echt netjes. Er zijn zeker constructies te verzinnen waarin het te verantwoorden is, of zelfs de makkelijkste oplossing. Het anders definieren van de verantwoordelijkheden is voor mij, tot nu toe, altijd een betere oplossing geweest :)
en dan hou ik van de ranzigheden die C mij kan bieden :P
.oisyn schreef op maandag 29 oktober 2007 @ 20:01:
[...]

En dat terwijl het C++ paradigima heel erg op referenties leunt ;). Ik gebruik pointers eigenlijk alleen als ze moeten kunnen veranderen of 0 kunnen zijn.
Een nare eigenschap van het leren programmeren in C. Voor de kleinere projecten waar ik tot nu toe aan heb gewerkt zijn pointers nog veilig genoeg. Zeker omdat ik redelijk veel met array's werk (array van pointers, inhoud kan varieeren), dan heb je eigenlijk al een pointer naar het juiste object van de array door array[i] mee te geven. Ik kan dan referenties gebruiken door dan *(array[i]) te doen, maar dat vind ik niet handiger. Als ik ergens pointers gebruik, dan ga ik niet snel over op referenties. Het ligt er dus maar net aan waar je mee bezig bent, en vooral, waar je aan gewend bent :)
Jaap-Jan schreef op maandag 29 oktober 2007 @ 20:43:
[...]

Waarvan het aanpassen van de declaratie nogal raar is en vaak nieteens mogelijk, bijvoorbeeld als je tegen een API van een derde partij programmeert. Daarom is het een beetje raar om te zeggen dat 'de declaratie fout zou zijn', imho.
De declaratie staat, zoals in de Topicstart beschreven en ik gebruikte voor het voorbeeld, was met pointers. Mijn reactie over het aanpassen van de declaratie was een reactie op de zin:
je moet dat doen door de signature van de functie aan te passen van void setA(A*) naar void setA(A).

  • sanderarts
  • Registratie: Oktober 2007
  • Laatst online: 30-10-2007
Bedankt voor de reacties, het werkt met het voorbeeld van Jaap-Jan.

Maar het vreemde was dat als ik het programma uitvoerde in de debugger gdb, waar ik breakte op lijn 8 in b.cpp
C++: b.cpp
8
this->a = newA


en in de debugger dan het commando
set this->a = newA
uitvoerde, dit wel goed ging.
Dus de class B werd wel degelijk geinitialiseerd en goed aangeroepen.
Maar op het punt waar de waarde van a daadwerkelijk werd gewijzigd, daar ging het fout.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 20:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

Er klopt niets van die conclusie. Omdat de debugger het wel kan wil nog niet zeggen dat het goed gaat. 'this' in B komt van A::b, wat een willekeurige ongeïnitialiseerde pointer is in de constructor van A. Je moet b eerst ergens naartoe laten wijzen voordat je 'm kan dereferencen.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.

Pagina: 1