[C++ (MS)] Virtual Destructors voor Interfaces Dilemma

Pagina: 1
Acties:

  • hobbit_be
  • Registratie: November 2002
  • Laatst online: 04-07-2025
Hoi

ben bezig met een cross-platfom realtime video libary (voor alle cameras, tuners) op windows, linux, mac en mobile platforms.

sinds ik windows gewoon ben heb ik de directshow implementatie (en VFW) het eerst geschreven.

om het toch lekker cross platform te houden heb ik een heel eenvoudig interface zodat de backend (VideoForlinux, Quicktime, ...) gewoon kan swappen zonder client code changes...

C++:
1
2
3
4
5
6
class IImageServer
{
     //*snip* 
public:
     virtual IImage* get_next() = 0;
}


zo kan al de client end code gewoon met de interfaces werken ipv 'native' solutions. Maar in c++ kom ik op dit probleem:

C++:
1
2
3
4
5
6
7
//ergens in de factory of #ifdef clause per platform... niet belangrijk.
IImageServer* t =
     reinterpret_cast< IImageServer* > (new DirectShowImageServer());  

//do all the stuff (no probs)

delete t; 


wat blijkt de destructor van t (DirectShowImageServer()) wordt NIET aangeroepen! en cleaned het object zelf niet op. Wat sinds er soms gebruik gemaakt wordt van callbacks en multiple threads soms tot een heel mooi accessviolation geeft.
Ik wel geen REF count gaan gebruiken want moet een lowel level API zijn en geen goesting voor het heruitvinden van DCOM. Trouwens je kan dit dan gewoon wrappen.

C++:
1
2
3
4
5
6
class IImageServer
{
public:
     virtual ~IImageServer() {}; //add virtual destructor
     virtual IImage* get_next() = 0;
}


nu gaat het wel MAAR nu is IImageServer een abstracte classe en geen echte interface.
en dat vind ik niet proper. Wat het eigenlijk zou moeten zijn lukt me langs geen kanten
om op te lossen:

C++:
1
2
3
4
5
6
class IImageServer
{
public:
     virtual ~IImageServer() = 0; //but pure doesn't work
     virtual IImage* get_next() = 0;
}


compiled gewoon niet (wat ik ook wel snap) maar wil dit zeggen dat je geen ECHTE destroyable interfaces kunt maken? en dat elke interface een soort van 'destroy' method moet hebben?

in Java heb je die issues natuurlijk niet want is allemaal REF based en ook in COM (windows) is een
interface altijd ref (vandaar dat __interface geen destructor allowed).

Wat denken dat ik best zou doen? method of abstract (met name op zicht voor cross platform)

offtopic:
wel effe anders dan ActionScript v2.0 :)

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:47

.oisyn

Moderator Devschuur®

Demotivational Speaker

Pure virtual destructors werken gewoon hoor. Je moet ze alleen nog wel implementeren, omdat de derived classes de destructor nog altijd aan willen roepen.

En wat is er nou erg aan een non-pure virtual destructor? Omdat het dan eigenlijk een abstracte class is ipv een interface? Wat voor onzin is dat? C++ kent geen verschil tussen class en interface, de waarde die jij eraan koppelt is puur synthetisch en bovendien erg persoonlijk.

[ Voor 4% gewijzigd door .oisyn op 04-08-2005 18:11 ]

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.


  • hobbit_be
  • Registratie: November 2002
  • Laatst online: 04-07-2025
Hmm kweet ook niet maar de error messages die ik krijg zijn weg : de laatste optie werkt dus wel.

Sorry voor de extensive post dus.

Qua onzin:

true: een echte interface bestaat niet in c++, maar het concept kan wel worden behouden (ie codeless) en ik verwacht dan ook dat als je ergens I.... doet er dan ook geen code mee betrokken is en nog meer geen cpp file of inlined code. Ben nogal purist op dat vlak en dat is idd totaal persoonlijk, waarschijnlijk net iets teveel scott meyers gelezen. :).

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Je moet wel een implementatie hebben voor een pure virtual dtor. Dit komt omdat elke dtor een implementatie moet hebben. Er zijn andere gevallen waarin een pure virtuele functie een implementatie heeft, maar bij dtors moet het.

  • hobbit_be
  • Registratie: November 2002
  • Laatst online: 04-07-2025
nu snap het ik het niet meer: is een implementatie van een pure virtual dtor geen tegenspraak - of bedoel je dat de class die het implement zeker een virtual dtor moet hebben.

om dit moment heb ik geen code voor de interface (met virtual ~dtor() = 0;). en de directorserver dtor word wel aangeroepen...

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

hobbit_be schreef op donderdag 04 augustus 2005 @ 21:08:
nu snap het ik het niet meer: is een implementatie van een pure virtual dtor geen tegenspraak - of bedoel je dat de class die het implement zeker een virtual dtor moet hebben.

om dit moment heb ik geen code voor de interface (met virtual ~dtor() = 0;). en de directorserver dtor word wel aangeroepen...
Ja dat klinkt als een tegenspraak (daarom vermelde ik het ook hier), maar dat is het niet. Een pure virtuele functie in een class zorgt ervoor dat die class abstract wordt, en dus niet geinstatieerd kan worden. Verder moet een pure virtuele functie door een subclass overridden worden (tenzij die class weer abstract is, etc) Maar er wordt niets gezegd over een implementatie. Die mag er best zijn, maar is meestal niet zo nuttig omdat deze niet wordt aangeroepen, maar de overridden versie van de subclass. Nou is het geval dat een destructor van een subclass ALTIJD de destructor van de base class impliciet aanroept. Jouw pure virtuele dtor gaat dus aangeroepen worden uit een subclass dtor, en heeft dus een implementatie nodig. Deze is overigens meestal gewoon leeg. Maar deze code is bv ook legaal:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A {
public:
   virtual void foo() = 0 {
      std::cout << "A::foo()";
   }
};

class B : public A {
public:
   virtual void foo() {
      std::cout << "B::foo()";
      A::foo();
   }
};

A* p = new B;
p->foo(); // prints "B::foo()A::foo()"

[ Voor 4% gewijzigd door Zoijar op 04-08-2005 21:29 ]


  • hobbit_be
  • Registratie: November 2002
  • Laatst online: 04-07-2025
nou dat wist ik niet :)

dus ik moet wel een {} erbij zetten of gaat de compiler dat zelf doen? ik snap dan ook niet waarom de code zonder implementatie nu wel werkt (al begin ik te vrezen voor cache probleempjes).

dus het enige nut voor een virtual dtor op een interface classe is ervoor te zorgen dat de delete netjes gebeurt (want zonder doet ie het niet - wat btw wel weird vind, houd ie dan niet bij dat als ik downcast (B -> A) en delete (A) doe hij toch eerst B dtor aanroept?

misschien is dit zoals iosyn aanhaalde het verschil: dat de c++ interface maar een fake is en dus echt als een classe beschouwd moet worden (what it is dat weet ik wel maar dat het ook practische gevolgen had, wat nu blijkt, niet).

thx voor de uitleg zoijar en oisyn

normaal zou ik bjourne uit de cast halen maar zit in het buitenland en geen boeken mee...

[ Voor 7% gewijzigd door hobbit_be op 04-08-2005 21:56 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

hobbit_be schreef op donderdag 04 augustus 2005 @ 21:55:
nou dat wist ik niet :)

dus ik moet wel een {} erbij zetten of gaat de compiler dat zelf doen? ik snap dan ook niet waarom de code zonder implementatie nu wel werkt (al begin ik te vrezen voor cache probleempjes).
Geef gewoon altijd elke destructor een implementatie, ook al is die leeg, dan zit je altijd goed. In principe moet je linker een error geven, en anders is je compiler misschien brak. Ik begin eerder te vrezen voor functie aanroepen midden in je data...
dus het enige nut voor een virtual dtor op een interface classe is ervoor te zorgen dat de delete netjes gebeurt (want zonder doet ie het niet - wat btw wel weird vind, houd ie dan niet bij dat als ik downcast (B -> A) en delete (A) doe hij toch eerst B dtor aanroept?
Een downcast is van A naar B ;) Daar heb je een dynamic_cast voor nodig; upcasting is van B naar A met een static_cast (of impliciet). Anyway... als je een derived pointer (B*) delete zal het goed gaan (eerst B::~B en dan A::~A; maar dit is gevaarlijk, wan als het eigenlijk weer een C is die derived van B gaat het weer mis... derive nooit van een class zonder virtuele dtor), maar als je een base pointer delete (A* met dynamisch type B ) zonder virtual dtor beland je direct in de wondere wereld van undefined behaviour... (logisch gezien zou alleen A::~A worden aangeroepen en B niet opgeruimd, maar je kan niet zeker weten dat het zo gaat. De standaard zegt dat het undefined is, dus...)
misschien is dit zoals iosyn aanhaalde het verschil: dat de c++ interface maar een fake is en dus echt als een classe beschouwd moet worden (what it is dat weet ik wel maar dat het ook practische gevolgen had, wat nu blijkt, niet).

thx voor de uitleg zoijar en oisyn
Mwja, niet als in java in ieder geval. Maar praktisch gezien wel hetzelfde. Je kan een class met een pure virtuele functie niet instantieren; dus in dat opzicht is het wel equivalent met een interface die je ook niet kan instantieren. Maar het is niet helemaal hetzelfde nee.

  • hobbit_be
  • Registratie: November 2002
  • Laatst online: 04-07-2025
heb per toeval (door te zoeken naar up/down cast - haal ik al jaren door elkaar en ben blijkbaar niet de enige :), ze hadden beter less_cast or more_cast moeten noemen, ik zie altijd een boomstructuur terwijl voor een downcast het een reverse boom is (pyramid)) nog een mooie uitleg + assembler gevonden voor al die dingen

http://docs.mandragor.org...ution/html/Chapter15.html

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Die virtual destructor gaat overigens niet werken samen met reinterpret_cast< >. Als je de cast nodig hebt, dan is de "derived class" geen derived class. Om het te laten werken moet in jouw geval de declaratie
C++:
1
 class DirectShowImageServer : public IImageServer { }; 
zijn. Dan he je een OO derived-to-base conversie, 100% veilig en dus zonder cast.

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


  • hobbit_be
  • Registratie: November 2002
  • Laatst online: 04-07-2025
Even nog een recap:

probleem met compiled was een cache probleem: ie herstart VStudio en weer de error (love it! ). Daarna dus alle pure virtual destructors met lege implementatie gemaakt. (heb nog effe restarted just in case :) )

Overigens was het een interface die op een andere interface was gebouwd ie:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class IImageServer
{
   virtual ~IImageServer() = 0 {}; 
     // 6 methods
}

class ILiveImageServer:
    public IImageServer
{
    virtual ~ILiveImageServer() = 0 {}; 
    //5 methods
};

class DirectShowLiveImageServer:
    public ILiveImageServer
{
     virtual ~DirectShowLiveImageServer(); //code in cpp
};

...

IImageServer* t = new DirectShowLiveImageServer();
delete t;


resulteerd in een mooie dtor na dtor... (in echt code wordt er gedebugged naar de output in VStudio).

vraag is nu alleen of het mooier zou zijn (of gewoon 'beter') om de twee interfaces totaal te scheiden van elkaar (ie de ene niet based op de andere) en de DirectShowclasse laat afleiden van beiden (maar dan heb je weer multiple inheritance wat toch weer af te raden is (scott meyers ed), maar misschien niet op 'interfaces' ?).

sorry als de vragen simpel zijn voor hardcore c++ proggers maar voor mij is het alweer een jaartje of twee geleden en dingen die ik in Java voor granted neem moet ik nu iets meer over nadenken :).

As always is de feedback op tweakers altijd fantastisch...

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:47

.oisyn

Moderator Devschuur®

Demotivational Speaker

Is een ILiveImageServer ook een ImageServer? In dat geval is je inheritance tree gewoon correct.

Overigens is die ; na de {} niet legaal achter een functie implementatie (of namespace for that matter)

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.


  • hobbit_be
  • Registratie: November 2002
  • Laatst online: 04-07-2025
.oisyn schreef op vrijdag 05 augustus 2005 @ 11:22:
Is een ILiveImageServer ook een ImageServer? In dat geval is je inheritance tree gewoon correct.

Overigens is die ; na de {} niet legaal achter een functie implementatie (of namespace for that matter)
Ja -> is gewoon een meer specifieke interface (maar moet wel de andere supporten).

thx ivm ';' na {}. VStudio (.2003) gaf me geen warning , maar meer stricte compiler zullen vast wel hinten dat die ';' pointless is? (ik neem aan dat dat beschouwd wordt als een lege statement?).

Nou nou wat duurt het weer een tijdje voor je weer in de groove zit.

Nu nog zien dat het allemaal gaat compilen op Linux / Mac... / Symbian OS

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:47

.oisyn

Moderator Devschuur®

Demotivational Speaker

hobbit_be schreef op vrijdag 05 augustus 2005 @ 11:39:
thx ivm ';' na {}. VStudio (.2003) gaf me geen warning , maar meer stricte compiler zullen vast wel hinten dat die ';' pointless is? (ik neem aan dat dat beschouwd wordt als een lege statement?).
Nee, het is gewoon niet legaal. Statements heb je in functies, buiten functies bestaan geen statements, dus ook geen lege. Vandaar dat het niet mag. In een functie zelf is er idd niets mis met een ; teveel :).

VC++ klaagt er idd niet over, gcc ook niet geloof ik. Hetzelfde voor de trailing komma na enum lijsten en initializer lists, die mogen eigenlijk ook niet, maar VC++ en gcc laten ze gewoon toe ;)

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.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

.oisyn schreef op vrijdag 05 augustus 2005 @ 12:03:
VC++ klaagt er idd niet over, gcc ook niet geloof ik. Hetzelfde voor de trailing komma na enum lijsten en initializer lists, die mogen eigenlijk ook niet, maar VC++ en gcc laten ze gewoon toe ;)
:o wist ik niet, klopt idd... stom zeg, dat is gewoon een bug eigenlijk. Comeau geeft wel gewoon een fout, zoals het hoort.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:47

.oisyn

Moderator Devschuur®

Demotivational Speaker

Zoijar schreef op vrijdag 05 augustus 2005 @ 13:06:
[...]

dat is gewoon een bug eigenlijk
Idd, want zelfs met language extensions gedisabled in MSVC++ 7.1 krijg ik nog geen error. Als het geen bug maar een feature was geweest dan hadden ze het wel op moeten nemen als language extension.

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.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

.oisyn schreef op vrijdag 05 augustus 2005 @ 13:16:
Idd, want zelfs met language extensions gedisabled in MSVC++ 7.1 krijg ik nog geen error. Als het geen bug maar een feature was geweest dan hadden ze het wel op moeten nemen als language extension.
Yep... ik snap op zich de extension wel, dat je naar C(++) kan compilen en je lijsten simpelweg outputten als copy(start, end, ostream_iterator(cout, ", ")) Maar... niet standaard. De Comeau error geeft ook al een beetje aan dat het wel een common extension is:

"ComeauTest.c", line 1: error: trailing comma is nonstandard
enum X {A,B,};

Oh well... doet Visual 2005 het ook fout? (ja... die doet het ook fout met language extensions disabled)

[ Voor 7% gewijzigd door Zoijar op 05-08-2005 13:58 ]


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
In de categorie stupide regels: ; na een functie declaratie is verboden, maar toegestaan na een in-class member functie declaratie. Tja.

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


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Zo die microsoft bug report site werkt ook lekker... not. Kom er niet eens in, blijft maar opnieuw sign in info vragen...

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
.oisyn schreef op vrijdag 05 augustus 2005 @ 12:03:
Hetzelfde voor de trailing komma na enum lijsten en initializer lists, die mogen eigenlijk ook niet, maar VC++ en gcc laten ze gewoon toe ;)
Flauwe reden: da's makkelijker scripten, en enum lijsten zijn vaker dan gebruikelijk gescript.

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

Pagina: 1