Avatar billede karas Nybegynder
20. januar 2009 - 11:03 Der er 16 kommentarer

Sammenligning af pointere til virtuelle funktioner

I C++ standarden står (med reference til de to operanter) følgende under afsnit ”5.10 Equality operators”: Otherwise if either is a pointer to a virtual function, the result is unspecified.

Jeg mener jeg har fundet en løsning, som gør at man kan sammenligne pointere til virtuelle funktioner. Dette spørgsmål beskriver denne løsning. Jeg ønsker blot at høre jeres kommentarer samt om i har nogen erfaring med noget lignende. Tak.

BESKRIVELSEN:

Jeg har lavet et bibliotek. En del af dets interface er nogle template klasser, der benytter funktions typer til deres specialisering samt tager en pointer til en funktion af den pågældende type. Da det er et bibliotek, har jeg ingen mulighed for at begrænse brugen af virtuelle funktioner.

IMPLEMENTERING

class ComparatorBase
{
public:
    virtual ~ComparatorBase() {}
    virtual bool equal(const ComparatorBase& base) const = 0;
};

template<class T>
class Comparator : public ComparatorBase
{
public:
    Comparator(T pfn) : m_pfn(pfn) {}

    bool equal(const ComparatorBase& base) const
    {
        const Comparator<T>* other = dynamic_cast<const Comparator<T>*>(&base);
        if (!other) {
            return false;
        }

        return m_pfn == other->m_pfn;
    }

private:
    T m_pfn;
};

Så vidt jeg kan forestille mig, så burde implementeringen af equal() kunne sammenligne vilkårlige funktions typer. Dette er baseret udelukkende på, at typen af funktionen bestemmes først. Kun member funktioner kan være virtuelle og to virtuelle funktioner fra samme klasse vil ikke kunne have samme adresse i denne her kontekst.

TEST

#include <assert.h>

class Base
{
public:
    virtual void foo() = 0;
    virtual void bar() = 0;
};

class Derived : public Base
{
public:
    virtual void foo() {}
    virtual void bar() {}
};

int main(int argc, char* argv[])
{
    Derived d;

    Comparator<void (Base::*)()> b_foo(&Base::foo);
    Comparator<void (Base::*)()> b_bar(&Base::bar);
    Comparator<void (Derived::*)()> d_foo(&Derived::foo);
    Comparator<void (Derived::*)()> d_bar(&Derived::bar);

    assert( b_foo.equal(b_foo) );
    assert( !b_foo.equal(b_bar) );

    assert( !b_foo.equal(d_foo) );
    assert( !b_foo.equal(d_bar) );
}
Avatar billede bertelbrander Novice
20. januar 2009 - 22:14 #1
Jeg ved ikke om det vil virke, jeg tvivler lidt på at det er garanteret at det altid vil virke.

For mig er det store spørgsmål: Hvad skulle formålet med at sammenligne funktions pointere være?
Avatar billede karas Nybegynder
21. januar 2009 - 10:57 #2
Mit ressonement er at når funktions pointeren bliver gemt i en variable uden for den kontekst hvor den er lavet, så må variablen indeholde en unik værdi for den pågældende type af pointer. Ellers ville der ikke være nogen garanti for hvilken funktion den kalder.
Avatar billede segmose Nybegynder
21. januar 2009 - 11:33 #3
Hvis jeg husker rigtigt så er en pointer til en virtuel funktion kun en henvisning til en indgang i den virtuelle table da den er kvalificeret med klasse navn dvs. der er ikke nogen direkte henvisning til den reelle adresse.

Men Bertel's spørgsmål er meget relevant, hvis du kan svar på "hvad er formålet?" kan vi måske angive et alternativ der gør det samme.
Avatar billede karas Nybegynder
21. januar 2009 - 15:11 #4
Ja, i nogle implementeringer indeholder en pointer til en virtuel funktion et index ind i v-tabellen. Men det burde min kode også kunne håndtere. Jeg er i tvivl om du med din kommentar er uenig?

Jeg vil godt forklare hvad jeg ønsker at lave, men der er ikke noget alternativ, hvis mit ønske til funktionalitet skal opfyldes - desværre.

Jeg er ved at lave et bibliotek som benytter functoides (et funktionskald repræsenteret ved et object) til kommunikation imellem tråde - i stedet for at bruge POD typer. Hved hjælp at lidt template triks kan man benytte funktionskald imellem tråde uden at skulle rode direkte med beskeder og ind og udpakning af parametre. Fx kan man fra tråd A få kørt en funktion i tråd B's kontekst med følgende:

call_async(
  b_thread_context,
  call_proxy(b_instance, &ClassB::foo, arg1, arg2)
  );

Den findes også i en call_sync version (blokerende).

Jeg har behov for at kunne sammenligne funktions pointere i det tilfælde at tråd B venter på en bestemt "besked" - som jo er repræsenteret af en funktions pointer.
Avatar billede bertelbrander Novice
21. januar 2009 - 19:29 #5
Overordnet set har du det problem at du bruger én variabel til at indeholde to informationer, dels adressen på en funktion, dels beskedstype, det går ikke.
Naturligvis har du et alternativ; at bruge to variabler til at overføre de to informationer med.

Jeg ville personligt smide ethvert bibliotek i skraldespanden der baserer sig på sammenligning af pointere til funktioner, uanset om dette måtte "virke" eller ikke, det er dårligt design.
Avatar billede karas Nybegynder
21. januar 2009 - 19:47 #6
Det "problem" du beskriver er præcist det kode eksemplerne "løser". Den "nummer to" variabel du siger jeg har brug for erv-tabellen (eller helt præcist RTTI informationen som v-tabellen referer til) i "beskeden".

"Beskeder" nedarver blot fra ComparatorBase, hvorefter Comparator<> kan bestemme om den har samme function som "beskeden".

Du er velkommen til at mene hvad du vil om at sammenligne pointere til funktioner, men hvis du har en reference til nogle artikler der beskriver dine betænkligheder ved brugen, så vil jeg meget gerne have dem. Det kunne være jeg kunne bruge dem til forbedre min kode.

Så længe sammenligningen af pointere til funktioner ligger indkapsuleret i biblioteket så kan jeg ikke se noget problem ved at bruge det. Du ville vel forhåbentligt heller ikke smide STL implementeringer i skraldespanden hvis de benytter void* casts ;-)
Avatar billede karas Nybegynder
21. januar 2009 - 19:53 #7
Mht. void* casts så tænker jeg på STL container implementeringerne
Avatar billede bertelbrander Novice
21. januar 2009 - 20:19 #8
Min betænkning går primært på at du bruger én variabel til at indeholde to informationer, at det til og med er en funktionspointer gør slet ikke sagen bedre. Du startede så fint med at citere fra C++ standarden, at resultatet af det du forsøger er "unspecified", så hvorfor forsøge at stole på noget der er "unspecified". Husk på at det kun er rent held/uheld at det du forsøger, synes at virke, efter din definition af "virke".

Hvorfor overfører du ikke blot to variabler, når du har brug for at fortælle modtageren to ting, må du fortælle de to ting, og ikke stole på at modtageren af sære omveje kan aflede den ene ting fra den anden?
Avatar billede bertelbrander Novice
21. januar 2009 - 20:49 #9
I øvrigt er det en dårlig ide at bruge RTTI til andet end debugging.
Avatar billede karas Nybegynder
21. januar 2009 - 20:57 #10
Jeg prøver kun at overføre en ting - en funktions pointer - ikke to ting. Jeg udnytter blot noget information om funktions pointeren for at kunne sammenligne den med en anden. Der er ikke en parameter mere jeg kan tilføje (som du foreslår) der kunne hjælpe med at løse problemet.

Jeg ved godt løsningen ikke er baseret på tvivlsom kode, men jeg har ikke noget alternativ. At noget er defineret i standarden som uspecifiseret betyder ikke at det ikke kan give et kendt resultat - resultatet er blot ikke specificeret af standarden. Det er jo ikke noget i vejen for at alle compilere supporterede det alligevel.

Mit håb med spørgsmålet var at kunne få nogle håndgribelige beviser for at det virker eller ikke virker. Mit næste skridt er at undersøge de compilere jeg ønsker biblioteket skal understøtte, og verificere at løsningen virker for dem.

Det ser "tilsyneladende" ud til at virke med gcc 4.1.2 og MSVC 2003.

Jeg værdsætter dine svar! Det er svært at finde nogen som kan diskutere dette...

Jeg er uenig med din holdning til RTTI, men det behøver vi vist ikke også diskutere :-)
Avatar billede torbenkoch Nybegynder
24. januar 2009 - 14:53 #11
Grunden til at de med de virtuelle metoder er "unspecified" er jo, at standarden intet siger om, hvordan compileren i praksis skal implementere virtuelle metoder. Det kan godt være, at dit kode virker lige netop på den compiler du bruger nu. Det betyder dog ikke, at det virker på een eneste anden compiler. Hvilket er i modstrid med din antagelse: "Det er jo ikke noget i vejen for at alle compilere supporterede det alligevel.". Jo, det er absolut i vejen for at alle compilere supporterer det. De har i forvejen problemer med at supportere alle de dele af standarden, som er "specified".

Æh, void* cast i STL? Kan da sagtens være du har ret, men jeg kunne ikke lige komme i tanke om et eksempel. Har du det?

RTTI bruges jo i forbindelse med dynamic_cast, hvilket til tider er en praktisk konstruktion.

Dine pure virtual i dit eksempel er farlige som test, da det er voldsomt forskelligt, hvordan compilere implementerer disse.

Men bortset fra C++ mærkværdighederne, så tilbage til dit reelle problem, som du beskriver helt uforståeligt:

At køre noget i en anden tråds kontekst? Hvordan forstår du selv den udtalelse? Man kan jo ikke bare bede en anden tråd om at holde op med at lave det, den laver lige nu, og så lave noget andet? I hvert fald ikke uden et eller andet message-system?

Man kan starte en ny tråd og bede den om at afvikle noget kode, hvor man f.eks. kan stikke den en pointer til en metode (ret oplagt).

Jeg forstår simpelthen ikke, hvad det er du forsøger at opnå?

PS. I C++ hedder det vist functors og ikke functoids?
Avatar billede karas Nybegynder
29. januar 2009 - 17:56 #12
Min udtalelse "Der er jo ikke noget i vejen..." vil jeg stadig mene er god nok. Det betyder ikke at jeg forventer alle gør det, det betyder at der ikke er noget der FORHINDRER dem i at gøre det. Du skal være opmærksom på at udtalelsen ikke går på at de 100% supporterer sammenligning af virtuelle functions pointere, men kun at MIT kode eksempel KUNNE fungere med de fleste compilere.
Avatar billede torbenkoch Nybegynder
29. januar 2009 - 19:20 #13
Forstår så udmærket, hvad du siger (er ikke mening, men skidt med det) - men fatter stadig ikke, hvad det er du forsøger at opnå - og det er jeg faktisk vildt nysgerrig efter ;-)
Avatar billede karas Nybegynder
29. januar 2009 - 22:50 #14
Jeg beklager, jeg havde ikke lige tid til at svare på det hele - blev lige afbrudt.

Jeg ved godt at det jeg har lavet er yderst betænkeligt - det var netop meningen med dette spørgsmål: om jeg kunne få be- eller afkræftet muligheden for at løse mit problem på den måde (eller en anden?). Jeg frygtede jeg ikke kunne få nogle konkrete svar, for det betyder jeg skal finde ud af præcis hvordan de forskellige kompilere implementerer virtuelle funktioner.

Jeg prøver at se om jeg kan forklare det jeg vil:

I stedet for at sende POD beskeder mellem tråde vil jeg sende functors (som du har ret i at det hedder). De functors som en tråd kan modtage nedarver selvfølgelig fra et message interface, så det er stadig en message.

Hvad opnår man så? Det smarte skulle være at din functor bliver autogenereret ved hjælp af templates. Følgende kode (som vist tidligere) er nok til at man fra tråd A sender en functor til tråd B, hvilket et message loop i tråd B kan dispatch'e når det får tid:

call_async(
  b_thread_context,
  call_proxy(b_instance, &ClassB::foo, arg1, arg2)
  );

Eksemplet træder i stedet for følgende som man normalt bliver nødt til at skrive:
<Tråd A>
a) Message definitionen (tit en POD struct med message ID og parametrene arg1 og arg2
b) Instantieringen af message'en
c) Initialiseringen af message'en
d) afsendelsen af message'en
<Tråd B>
e) bestemme typen af message'en (ud fra ID'et)
f) udtage arg1 og arg2 fra message'en og kalde ClassB::foo(arg1, arg2)
g) deallokere message'en

Punkt a) til d) bliver udført kaldet til call_async(...) og punkterne e) og g) kan implementeres i en generel klasse som kun behøver at kende til base klassen for alle functors. Punkt f) implementeres af functoren, som blev autogenereret af call_proxy(...).

Hvornår har man så brug for at sammenligne virtuelle functions pointere? Jo, det skulle jo gerne være muligt at vente på en bestemt besked, men da en besked ikke er en POD struct med e.g. et message id som man kan cast'e ud fra, hvad så? Den information som udgør "message id" for en functor er funktionens type og det er derfor at jeg har brug for at kunne sammenligne pointere til virtuelle funktioner.

Dvs. kort fortalt ønsker jeg at sende functors i stedet for POD messages mellem tråde, og for at kunne vente på en bestemt functor (som man kan vente på en bestemt besked) så har jeg brug for at kunne sammenligne alle slags funktions pointere - inklusiv pointere til virtuelle funktioner.

Jeg håber det giver mere mening nu, ellers må du skrive igen.
Avatar billede torbenkoch Nybegynder
29. januar 2009 - 23:45 #15
Ah ja, jeg tror nogenlunde jeg fanger din ide.

Istedet for at forlade dig på virtuelle metoder, som nogle mener ikke skal være en del af en klasse public interface, så kunne du måske gøre noget i denne stil:

class A : public SomeThreadThingy
{
  public:
    void MethodToCallFromOtherThread()
    {
      DoMethodToCallFromOtherThread();
    }

  protected:
    virtual DoMethodToCallFromOtherThread()
    {}
}

Så vil du kunne sammenligne med pointeren til den ikke-virtuelle metode i stedet.

Ville det løse dit problem?
Avatar billede karas Nybegynder
30. januar 2009 - 08:17 #16
Ja, det har du ret i vil virke, og det kan være det bliver udgangen. Jeg ønsker bare ikke at lave den begrænsning i biblioteket (at man IKKE må give call_proxy() en pointer til en virtuel funktion) - specielt når der ikke findes nogen (kendt hvert fald) metode til at bestemme compile time om en pointer er virtuel. Dvs. jeg kan ikke forhindre at brugeren benytter interfacet til biblioteket forkert og han vil se det som en runtime fejl. Det er ikke acceptabelt.
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester