[C++] this-pointer wijst naar base ipv derived

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • JeromeB
  • Registratie: September 2003
  • Laatst online: 14-08 21:56
Voorbordurend op mijn vorige topic: [C++]return-waardes interpreter-pattern.

Ik ben vandaag aan de slag geweest met het visitor-pattern en ik heb mijn expression-classes iets verandert. Ik heb een visitor-class en mijn expression-classes hebben nu een methode accept(). Deze methode ziet er voor iedere expression-class hetzelfde uit. Ik moet hem echter telkens opnieuw toevoegen, want anders verwijst de this-pointer telkens naar een base-object ipv het derived-object. Dit is ietwat krom verwoord, daarom ik zal het proberen te verduidelijken middels wat code.

Voor de duidelijkheid, dit probleem gaat dus niet specifiek over het visitor-pattern, maar over inheritance.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class base_expression;
class derived_expression;

class visitor
{
    public:

    virtual void visit(base_expression* bexp) = 0;
    virtual void visit(derived_expression* dexp) = 0;
};

class base_expression
{
    public:

    // ik moet deze methode voor elke expression-class opnieuw toevoegen
    virtual void accept(visitor& v)
    {
        v.visit(this);
    }
};

class derived_expression : public base_expression
{
    public:

    // als ik deze methode weg laat wordt visit(base_expression*) aangeroepen ipv visit(derived_expression*)
    void accept(visitor& v)
    {
        v.visit(this);
    }
}

class printer : public visitor
{
    public:

    void visit(base_expression* bexp)
    {
        std::cout << "base_expression" << std::endl;
    }

    void visit(derived_expression* dexp)
    {
        std::cout << "derived_expression" << std::endl;
    }
};

void test()
{
    base_expression* bexp = new base_expression();
    derived_expression* dexp = new derived_expression();
    printer p;

    bexp->accept(p);
    dexp->accept(p);
}


uitvoer met accept() in derived_expression:
base_expression
derived_expression

uitvoer zonder accept() in derived_expression:
base_expression
base_expression

Ik kan dus voor iedere expression-class een methode accept() toevoegen, maar het irriteert me een beetje. Is het mogelijk om mijn accept-methode alleen in de base-expression-class te definiëren?

[edit]
Overigens heb ik even door de C++ FAQ Lite lopen spitten, maar volgensmij zie ik iets over het hoofd :+

[ Voor 4% gewijzigd door JeromeB op 19-04-2009 22:59 ]

PC load letter? What the fuck does that mean?


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 16:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

Nee. Het hele idee van het visitor pattern is juist dat je gebruik maakt van compile-time type info om de juiste functie-overload te kieze. Dit impliceert dat iedere class z'n eigen accept() moet implementeren. Je kunt hoogstens gebruik maken van een template class of een macro

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
template<class Derived, class Base = void>
class accepter : public Base
{
public:
    virtual void accept(visitor & v) { v.visit(static_cast<Derived*>(this));
};

// specialisatie voor root classes die nergens van overerven
template<class Derived>
class accepter<Derived, void>
{
public:
    virtual void accept(visitor & v) { v.visit(static_cast<Derived*>(this));
};


// Je class hierarchie
class base_expression : public accepter<base_expression>
{
};

class derived_expression : public accepter<derived_expression, base_expression>
{
};

En de macro optie is natuurlijk vrij straightforward, waarbij je een macro maakt met de implementatie van accept() erin die je vervolgens alsnog in elke class moet vrotten :)

Overigens heb ik voor m'n proton compiler even snel een tooltje geschreven die een class hierarchy kan parsen en daarvoor een visitor interface en accept methods kan genereren, tezamen met een visitoradaptor class die de visitor implementeert en voor elke visit(derived*) de call doorroute naar visit(base*). Maar goed, met een class hierarchie van 50+ classes is dat ook wel fijn, zeker als je op een bepaald punt besluit dat je huidige visitor nog niet helemaal voldoet (zo heb ik elke visit() omgezet naar previsit()/postvisit() paren wat wat handiger werkt als je een boomstructuur moet visiten. Ik moet er niet aan denken dat ik dit allemaal handmatig zou hebben moeten veranderen :P)

[ Voor 28% gewijzigd door .oisyn op 20-04-2009 01:35 ]

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.


Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
.oisyn schreef op zondag 19 april 2009 @ 23:13:
Overigens heb ik voor m'n proton compiler even snel een tooltje geschreven die een class hierarchy kan parsen en daarvoor een visitor interface en accept methods kan genereren, tezamen met een visitoradaptor class die de visitor implementeert en voor elke visit(derived*) de call doorroute naar visit(base*). Maar goed, met een class hierarchie van 50+ classes is dat ook wel fijn, zeker als je op een bepaald punt besluit dat je huidige visitor nog niet helemaal voldoet (zo heb ik elke visit() omgezet naar previsit()/postvisit() paren wat wat handiger werkt als je een boomstructuur moet visiten. Ik moet er niet aan denken dat ik dit allemaal handmatig zou hebben moeten veranderen :P)
Persoonlijk ga ik in zulke gevallen eigenlijk altijd voor een Acyclic Visitor (of een aangepaste variant).

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class visitor{
public:
    // this typedef should be overwritten by sub classes
    typedef base node_type;

    virtual ~visitor() {}
};

template<typename _Tv>
class base{
public:
typedef _Tv visitor_type;

//! Accept a visitor for this node
virtual void accept(visitor *a_Visitor){
    // if we can cast this visitor to a concrete visitor (_Tv)

    if(visitor_type *l_Visitor = dynamic_cast<visitor_type*>(a_Visitor)){
        // and if this node corresponds to the node 
        // accepted by the concrete visitor

        typedef typename _Tv::node_type node_type;
        if(node_type *l_This = dynamic_cast<node_type*>(this)){
            // then visit the node
            l_Visitor->visit(l_This);
        }
    }
}
};


C++:
1
2
3
4
5
6
7
8
9
10
class foo_visitor : public visitor{
 public:
        typedef foo_node node_type;
        virtual void visit(node_type *a_Node) = 0;
 };

 class foo_node : public base<foo_visitor>{
 public:
     // node implementation
 };


De concrete visitors erven dan van (alle) base visitors die ze willen implementeren; zodoende blijf ik in ieder geval niet zitten met visitors die niet meer willen compilen als ik de class hierarchie uitbreid of aanpas. Ook maakt het de visitoradapter uit jou voorbeeld overbodig omdat dit opgelost word in de inheritance tree van de visitor interfaces.

Enige nadeel wat ik kan zien, is dat er nu voor iedere node nog een extra class bij komt; de visitor interface. En natuurlijk dat RTTI, zoals in dit voorbeeld geimplementeerd, nodig is.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 16:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ik vind je voorbeeld een beetje onhandig gekozen, aangezien je maar 1 concrete klasse hebt. Ik snap niet helemaal hoe het nou werkt met een klasse hierarchie. Stel ik heb een foo_derived die erft van foo_node. Moet dat gewoon op de normale manier van foo_node overerven? Als dat zo is, dan werkt de visitor iig al niet, want base<foo_visitor> gaat nooit proberen te casten naar een foo_derived. Of moet ik voor foo_derived ook een foo_derived_visitor definieren, en moet foo_derived dan van een foo_node en een base<foo_derived_visitor> overerven? Op dat moment werkt de accept() niet meer omdat die ambigu is.

Ook snap ik niet waarom er een visitor base type is. Voor de base<T>::accept() is ie niet nodig, want dat is sowieso een template functie dus die kan net zo goed een T* verwachten (het zou pas nuttig zijn als base<T> zelf ook weer overerft van een non-template base class). Dit betekent tevens dat de buitenste dynamic_cast in die accept() implementatie ook niet meer nodig is.

Ik heb het artikel doorgelezen, maar ook daar spreken ze niet van een diepere nesting dan 1 klasse. Bovendien werkt de hierarchie niet zoals ik 'm heb opgezet. Als ik even het voorbeeld uit dat artikel aanhou, en ik heb een ZoomModemVisitor, dan wordt niet de Visit(Modem*) aangeroepen (die bestaat ook niet) als het toevallig een HayesModem betreft. Maar dan zou je evt. de Accept() implementatie van de base class aan kunnen roepen (mocht die bestaan, in Modem dus), als de dynamic_cast in de Accept() van de derived class niet lukt.

Daarnaast blijft het zo dat je alsnog al je classes handmatig in een visitor interface moet stoppen. Daar heb ik dus die tool voor :). En of ie dan een acyclic visitor of een traditionele genereert doet er verder niet toe. Het nut van de acyclic visitor valt daarmee wel een beetje af, aangezien toch alles automatisch gegenereerd wordt.

[ Voor 119% gewijzigd door .oisyn op 20-04-2009 15:01 ]

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.


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 16:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ik hoopte eigenlijk nog op een toelichting van PrisonerOfPain... ;)

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.