Newsgroup: it.comp.lang.c++
Titolo: oggetti extern e ereditarietà

salve a tutti
ho un problema un pochino complesso (almeno per me...) che mi sta portando
alla pazzia :o)
sto scrivendo una libreria di classi (un framework piccolino x win32).
l'entry point del programma (WinMain) si trova in un sorgente che fa parte
della libreria.
da WinMain devo richiamare dei metodi di una classe (Application) dichiarata
nello scope globale.
questa classe perň non č definita nella mia libreria, o meglio č definita la
classe base, ma chi usa la libreria per scrivere un programma deve ereditare
una classe da Application e fare l'override dei metodi  virtuali InitInstance
e ExitInstance.
allora io ho pensato di scrivere (nel modulo dove c'č l'entry point del
programma):
extern xwin::Application theApp;
e poi il programmatore che usa la libreria (io ;P) costruisce un nuovo oggetto
derivato da Application e dichiara un'istanza nel namespace globale:
class MyApp : public xwin::Application { ... };
MyApp theApp;
ma quando vado a compilare VC dice che
test.cpp(3) : error C2371: 'theApp' : redefinition; different basic types
        xwin\xwin.h(8) : see declaration of 'theApp'
che posso fare?
non c'č un modo di aggirare questo controllo sui tipi?
o sono costretto a usare ancora le macro?
grazie
ciao
xaero


Forse una soluzione potrebbe essere usare un puntatore:
xwin::Application* pTheApp;
e nel/i costruttore/i aggiungi
xwin::Application()
:...
{
 ...
 pTheApp = this;
}
eppoi Replace everywhere "theApp." con "pTheApp->"
non so se risolve il pb.
Ciao
Massimo

 eppoi Replace everywhere "theApp." con "pTheApp->"
ci avevo già pensato.
ma se dichiaro un puntatore anziche una classe, nell'entry point del programma
dovrei scrivere:
extern xwin::Application * pTheApp;
int __stdcall WinMain(...)
{
    pTheApp = new xwin::Application;
    if( ! pTheApp->InitInstance() )
        return -1;
    pTheApp->RunMessageLoop();
    return pTheApp->ExitInstance();
}
questo funzionerebbe senza problemi se xwin::Application è una classe da
utilizzare cosi' com'è.
ma invece xwin::Application va usata solo come oggetto base per fare
l'override di Init/ExitInstace, e la classe theApp (o pTheApp) la dichiara il
programmatore che usa la libreria.
mettiamo che eredito la classe CSimpleApp da xwin::Application. in WinMain
però uso il costruttore di xwin::Application per allocare memoria per
l'oggetto.
è giusto?
grazie lo stesso per l'attenzione
ciao
xaero

la storia del puntatore non funziona. da lo stesso errore di prima.
ho dovuto usare una macro che scrive l'entrypoint e dichiara una classe nel
namespace globale.
tuttavia non sono così addolorato se penso alle macro che vengono usate in
MFC... almeno la consolazione.. :)))
ecco come ho fatto (ometto i parametri di winmain che sono lunghi e inutili):
#define DECLARE_APP_TYPE(className) \
 className *pTheApp; \
 int __stdcall WinMain(...) \
 { \
 className theApp; \
 pTheApp = &theApp; \
 if(!theApp.InitInstance()) \
 return -1; \
 theApp.RunMessageLoop(); \
 return theApp.ExitInstance(); \
 } \
 namespace xwin { \
  inline className * GetAppObject() { return pTheApp; } \
 };
questa macro la uso dopo aver definito (non implementato) la classe
application:
class MyApplication : public xwin::Application {
    BOOL InitInstance();
    int ExitInstance;
};
DECLARE_APP_TYPE(MyApplication)
ora c'è un puntatore a MyApplication (e non a xwin::Application) nel namespace
globale, e in xwin:: c'è una funzione GetAppObject che mi da il puntatore
all'oggetto MyApplication
(a differenza di MFC che ti dà un puntatore a CWinApp che poi è da cast-are).
mmhh... tuttavia sono soddisfatto :oP
grazie lo stesso a tutti
se qualcuno ha in mente una soluzione migliore sono lieto di ascoltare.
--
ciao
xaero

 }
Non proprio. Massimo suggeriva di dotare xwin::Application della capacità
di autoregistrarsi come pTheApp nel momento in cui viene instanziata.  
L'utilizzatore quindi deriverà una classe da xwin::Application e definirà
un oggetto statico. WinMain quindi userà pTheApp senza instanziare
alcunchè.
Ad ogni modo, questo modello non mi piace per due ragioni:
 1. richiede l'impiego di oggetti statici
 2. sfrutta l'eredità di implementazione
Mi rendo conto che alcuni potrebbero non condividere il punto 2.
Max


La mia idea iniziale era questa:
nella libreria
xwin::Application* pTheApp = 0; // o NULL
int __stdcall WinMain(...)
{
    // senza new
     if( ! pTheApp->InitInstance() )
         return -1;
     pTheApp->RunMessageLoop();
     return pTheApp->ExitInstance();
}
e nel modulo "user"
MyApp theApp;
il costruttore di questa chiamerebbe il ctor della base nella quale
tu assegni il this (cioè &theApp) anche a pTheApp (sia theApp che pTheApp
globali) quando la Winmain viene chiamata pTheApp punterebbe già alla
applicazione derivata.
IMHO, ritengo però che usare degli oggetti globali sia poco "robusto",
soprattutto se sono + di uno, se nel costruttore poni del codice o fai
riferimento ad altri oggetti globali. xe' non vi e' certezza dell'ordine con
cui vengono costruiti/allocati.
Un'alternativa potrebbe essere quella di costringere l'user oltre
al resto a definire una funzione che wrappa l'operator new
modulo "user"
xwin::Application* CreateApplication()
{
  return new MyApp;
}
"libreria"
extern xwin::Application* CreateApplication();
int __stdcall WinMain(...)
{
  xwin::Application* pTheApp = CreateApplication();
  if( ! pTheApp->InitInstance() )
    return -1;
  pTheApp->RunMessageLoop();
  int iReturn = pTheApp->ExitInstance();
  delete pTheApp // ovviamente virtual destructor per xwin::Application
  return iReturn;
}
Ciao
Massimo

 se qualcuno ha in mente una soluzione migliore sono lieto di ascoltare.
Fammi capire, tutto questo per sollevare l'utilizzatore dal compito di
dover scrivere il corpo di WinMain? Uhm...
Non č meglio qualcosa del genere:
 template< class APP >
 class AppRunner
 {      
       public:
	static int run()
        {
          APP theApp;
          if(!theApp.InitInstance()) return -1;
          theApp.RunMessageLoop();
          return theApp.ExitInstance();
        }
 };
Poi l'utilizzatore dovrŕ scriversi un piccolo WinMain:
 int __stdcall WinMain(...)  { return AppRunner<MyApp>::run(); }
Beh, puoi farci una macro qui, se vuoi. :-)
Max

o sono costretto a usare ancora le macro?
Invece di usare una classe instanziata con durata statica usa un
puntatore. Potresti addirittura fare qualcosa come:
   //// Nella libreria
   namespace xwin {
   class Application
   {
     public:
       static Application *instance;
       static int instance_count;
       Application()
       {
         instance = this;  // salva il puntatore nel costruttore
         ++instance_count; // contatore
       }
       void Run();
   };
   }
   int WINAPI WinMain(... blah blah blah ...)
   {
     ///
     if (xwin::Application::instance_count == 1)
     {
       ... tutto ok ...
       xwin::Application::instance->foo();
     }
     else
     {
       ... Whoops ... nessuna applicazione oppure troppe
     }
   }
   //// Nel programma che usa la libreria
   class MyAppl : public xwin::Application
   {
     ....
   };
   myAppl appl;
Ha senso ?
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff


Io direi di si, ovviamente dopo avere opportunamente inzializzato instance e
instance_count (visto che sono public, potresti dichiararle private e friend
la WinMain oppure incapsularle con qualche metodo inline), giusto per
cautelarsi rispetto ad un uso poco consapevole della libreria.
Cosa intendi per "classe istanziata con durata statica" ?
Ciao
Massimo

 instance e instance_count
Non mi è chiaro, però, come si possa garantire che il costruttore di
'myAppl appl' sia invocato *dopo* che, ad esempio, instance_count sia
stato inizializzato, essendo essi presumibilmente definiti in due diverse
unità di compilazione.
Max


 'myAppl appl' sia invocato *dopo* che, ad esempio, instance_count sia
Sempre IMHO io penso che se tu scrivi
int xwin::Application::instance_count = 0;
questo possa essere realizzato at link-time.
Peraltro, questa è una soluzione che io non adotterei proprio perchè confida
sull'uso di oggetti globali, statici se preferisci. E soprattutto in
un'ottica di riutilizzo, nulla vieta all'user di inserire nella classe
derivata altri oggetti, magari statici i cui costruttori potrebbero
sollevare eccezioni.. o chissà che altro.
Alla fine sia la soluzione della macro che la tua istanziano l'oggetto
nel WinMain (che xò non è + nella libreria) soluzione sicuramente +
ortodossa.
Ciao
Massimo

Cosa intendi per "classe istanziata con durata statica" ?
Intendevo semplicemente una istanza globale... il problema
dell'OP era che il linker (giustamente) si arrabbia se lui
dichiara una istanza extern globale di una classe e invece
l'istanza e' di una classe derivata.
Con il sistema di salvarsi l'indirizzo dell'istanza nel
costruttore della classe base poteva risolvere il problema
di trovare questa istanza nel suo winmain tramite un
puntatore.


Max
L'azzeramento delle variabili con durata statica avviene sempre
prima di ogni altra cosa (3.6.2 : basic.start.init / 1 sui miei
documenti non ufficiali). Per i costruttori le cose sono piu'
complicate...
Io ne deduco che:
   // Modulo X
   int *x;
   // Modulo y
   extern int *x;
 
   class Y
   {
     public:
       Y()
       {
         ... in questo punto x e' sicuramente il
         puntatore nullo ...
       }
   };
   Y y_instance;
Sostanzialmente il problema dell'ordine di inizializzazione
delle istanze statiche riguarda la costruzione completa e
non la "zero-initialization" (come chiamata dallo standard):
quella avviene sempre e comunque prima.


 non c'è un modo di aggirare questo controllo sui tipi?
