SOFTWARE > Linguaggi di programmazione e scripting

[C++] differenze nelle implementazioni dei compilatori

(1/2) > >>

TheKaneB:
Giusto un piccolo "rant" che mi frullava per la testa: le implementazioni attuali di C++ (ho avuto la sfortuna di lavorare su progetti multipiattaforma compilati con 3 compilatori diversi) sono discordanti in alcuni punti, soprattutto hanno differenze nel definire lo scope degli identifier delle enum e dentro i template.

Ad esempio, con le enum ho notato queste differenze di implementazione:


--- Codice: ---class Prova
{
  public:
  enum Ciao
  {
    UNO,
    DUE
  }
};

Prova::Ciao x = Prova::Ciao::UNO; // ok con MSVC2005, errore con GCC e SNC
Prova::Ciao y = Prova::UNO; // ok con tutti

Prova::Ciao z = x+1; // ok con MSVC2005 e GCC (però da un warning), errore con SNC

--- Termina codice ---

Con i template invece ho notato questa differenza tra i compilatori:

--- Codice: ---
template <typename T>
class Cane<T>
{
    protected:
    T mCoda;
};

template <typename T>
class Prova<T> : public Cane<T>
{
    Prova<T>(const T& parametro)
    {
      mCoda = parametro; // Errore su GCC, ok su MSVC2005 e su SNC
      this.mCoda = parametro; // Ok su tutti
    }
};

--- Termina codice ---

Su progetti molto grossi, con un'architettura composta da decine di migliaia di classi, ed uso estensivo dei template e delle enum, queste differenze hanno comportato non pochi grattacapi... In particolare lo sviluppo avveniva prettamente su MSVC2005 (target build Windows e XBox360) e se tutto andava a buon fine, si ricompilava per GCC e SNC (target build PS3). Successivamente abbiamo droppato il supporto a GCC, e usato soltanto SNC perchè più veloce a compilare e produce codice più ottimizzato (testato ad "alto livello" tramite un profiler).

TheKaneB:
per il discorso delle enum, anch'io preferisco l'implementazione di MSVC.

per i template, invece, dovrebbero valere le stesse regole di scoping che valgono nelle normali classi. In particolare le variabili globali hanno la priorità più bassa in assoluto.

I parametri mascherano le variabili membro (diretti o ereditati), e le variabili membro mascherano le variabili globali.
L'uso del "this" dovrebbe quindi essere implicito, e difatti lo diventa se quello stesso codice lo implemento come classe normale, mentre si incazza se lo implemento come template.

inoltre si incazza solo per i membri ereditati, mentre rende correttamente il this implicito per i membri diretti.

Misteri...  :shock:

TheKaneB:

--- Citazione da: "dsar" ---Onestamente mi sembra contortissima questa gestione degli scope
--- Termina citazione ---

sullo Stroustrup ci sono tipo 100 pagine dedicate solo alla risoluzione degli scope... per evitare problemi mi sono autovietato di usare "using namespace" e uso SEMPRE il full scope di ogni cosa, e buona notte...

sarà più prolisso da scrivere, ma evita qualsiasi bug subdolo dovuto a name clashing...

TheKaneB:

--- Citazione da: "dsar" ---
--- Citazione da: "TheKaneB" ---
--- Citazione da: "dsar" ---Onestamente mi sembra contortissima questa gestione degli scope
--- Termina citazione ---

sullo Stroustrup ci sono tipo 100 pagine dedicate solo alla risoluzione degli scope... per evitare problemi mi sono autovietato di usare "using namespace" e uso SEMPRE il full scope di ogni cosa, e buona notte...

sarà più prolisso da scrivere, ma evita qualsiasi bug subdolo dovuto a name clashing...
--- Termina citazione ---

Anche il caso da te detto prima andrebbe messo sotto scope esplicito, non ho capito perché dare alle variabili globali il livello più basso,

--- Termina citazione ---
In caso di name clashing, infatti, DEVI usare la sintassi
::identificatoreGlobale
per specificare che vuoi la variabile globale e non quella della classe, proprio perchè queste hanno priorità minima.

--- Citazione ---
ci sono casi semplici in cui non vale la pena usare le classi. Sarà che ho sempre usato linguaggi OOP con il module as manager (Ada) e non il module as type (come il class del C++).

--- Termina citazione ---
Per come è fatto il C++, meglio usare SEMPRE le classi, anche per tipi triviali (anche un banale array, ad esempio).
Infatti, se in un secondo momento volessi fare qualcosa di più complesso (ad esempio usare un custom allocator) puoi ridefinire gli operatori di quella classe, cosa che non potresti fare con i normali tipi base.
Spesso è conveniente ridefinire i costruttori per copia e l'operatore di assegnazione, soprattutto quando si ha a che fare con tipi in forma di array/matrici, per gestire meglio la memoria ad esempio se si vuole implementare un meccanismo di copy-on-write, oppure se si vuole implementare il reference counting oppure ancora per implementare dei "weak pointers" (cioè puntatori ad oggetti sacrificabili, magari ricreabili a partire da altri dati, usati soprattutto per implementare sistemi di cache temporanea con garbage collecting).

Tutta roba che sarebbe comodo avere lato linguaggio, e invece con il C++ mi è toccato implementarlo manualmente. Tuttavia forse è anche questo uno dei lati positivi di questo linguaggio, ovviamente contestualizzando la cosa allo sviluppo dei videogames dove bisogna avere un controllo necessariamente manuale delle risorse (per tutta una sfilza di motivi che non sto qua a citare).

--- Citazione ---In ogni caso il discorso è semplice, tu non devi impossibilitare i tuoi client l'uso dei nomi delle variabili che vogliono perché la tua classe li occupa. Questo non vale solo per le variabili globali, ma anche per chi vuole estendere la tua classe.

--- Termina citazione ---
Per questo hanno introdotto lo "using namespace", così dici al compilatore di privilegiare un namespace al posto di un altro, scavalcando il name scoping automatico. Tuttavia preferisco usare il full scope esplicito proprio per evitare che un programmatore "burlone" faccia porcherie con i nomi dei membri. Chiaramente considera il caso di un progetto sviluppato da 40 persone con livelli di preparazione, diciamo, "eterogenei" :-D Meglio imporgli una regola strict (e bacchettarli quando sgarrano, visto che i commit sul server sono tracciati per username), piuttosto che lasciargli usare tutte le caratteristiche di un linguaggio infernale come il C++...


--- Citazione ---Comunque tornando al discorso dei template e classi, in effetti è molto incoerente
--- Termina citazione ---

In GCC ho trovato tanti altri bug sui template (almeno fino alla versione 4.2), ad esempio c'era un bug bastardo sui template di classi virtuali pure che non generava errori a compile time, ma creava codice errato che andava a referenziare una virtual table inesistente, generando quindi un errore di null pointer a runtime (fortunatamente era solo un null pointer, ma poteva anche essere memoria random!).
Adesso non ricordo esattamente come replicare il bug, il codice era un po' contorto, però in sostanza bisognava creare una classe template B derivata da una classe template virtuale pura A (sostanzialmente, una Interface in gergo Java), e poi non ricordo in che modo si creava questo bug a runtime quando si chiamava un metodo virtuale di C, che derivava da B implementando l'interfaccia di A.

Forse si scatenava con l'ereditarietà multipla, non ne sono sicuro, visto che comunque usavamo queste classi virtuali pure template alla stessa maniera delle Interfaces del Java, quindi capitava di avere classi del tipo:


--- Codice: ---class Pilota : public Subscriber<PhysicsEventPublisher>, public Subscriber<NetworkEventPublisher>
{
...
};

--- Termina codice ---

per evitare tali problemi con quel bug di GCC, il codice veniva aggiustato in questo modo:

--- Codice: ---class Pilota
{
private:
  Subscriber<PhysicsEventPubliser> mPhysicsSubscriber;
  Subscriber<NetworkEventPublisher> mNetworkSubscriber;
};

--- Termina codice ---
In pratica nel secondo caso, evitando l'ereditarietà multipla, le virtual table venivano generate correttamente da GCC e funzionava tutto.
Ovviamente questo comportava piccole modifiche al codice di gestione degli eventi, ma roba trascurabile.
MSVC invece funzionava correttamente in entrambi i casi...

clros:

--- Citazione da: "TheKaneB" ---per il discorso delle enum, anch'io preferisco l'implementazione di MSVC.


--- Termina citazione ---

Illuminatemi: perchè non dovrebbe essere corretto l'esempio che hai riportato con gli enum? Per me (e per le mie poche conoscenze) è "logico", non capisco perche GCC non lo compili...

Navigazione

[0] Indice dei post

[#] Pagina successiva

Vai alla versione completa