Cosa mi dici sull'ultimo post che ho inserito??
Cosa mi dici sull'ultimo post che ho inserito??
Con i sogni possiamo conoscere il futuro...
Come già detto, è undefined behavior; stai violando le regole del linguaggio, dato che stai richiamando un metodo d'istanza su una istanza non valida, per cui tutto può succedere. Il C++ non effettua alcun controllo sostanzialmente per motivi di prestazioni.
Per chiarire come mai nel tuo caso tutto sembra funzionare, bisogna guardare "sotto il cofano" e vedere come sono implementate effettivamente le classi.
Nella maggior parte delle implementazioni, una classe, a livello di codice generato, è equivalente ad una struct contenente i suoi campi e a delle "normali" funzioni libere che ricevono il "this" (ovvero, l'istanza su cui operare) come parametro nascosto. Per questo motivo, codice di questo tipo:
Di fatto è come se fossecodice:class A { int a; double b; void doSomething(int value) { a=value; b=value*2.5; }; }; ... A test; test.doSomething(15);
La faccenda in realtà si complica un po' quando ci vanno di mezzo funzioni virtuali, i distruttori, ereditarietà multipla e compagnia, ma il concetto di fondo è sempre questo - quando chiami un metodo su un'istanza, di fatto stai chiamando una "normale" funzione passandole l'istanza su cui deve operare come parametro "nascosto".codice:struct A { int a; double b; }; void A_doSomething(A *this, int value) { this->a=value; this->b=value*2.5; } ... A test; A_doSomething(&test, 15);
Venendo al tuo caso, è chiaro a questo punto come mai non incontri errori eclatanti: la tua classe ha un metodo test che non fa alcun uso del puntatore this (di fatto potrebbe essere tranquillamente un metodo static), per cui, anche se richiamato su un'istanza non valida (o addirittura NULL), non si accorge nemmeno del problema, dato che riguarda un dato di cui non fa alcun uso.
Ribadisco comunque che questo non è assolutamente un comportamento supportato, e va considerato una circostanza fortunata (o meglio, sfortunata, dato che nasconde un bug) il fatto che in questo caso non ottieni un crash. Oltre al fatto che basta cambiare qualcosa (rendi il metodo virtuale, usa in qualche maniera un membro della classe) e subito si rompe, può anche indurre l'ottimizzatore a fare assunzioni errate riguardo al tuo codice.
A tal proposito (e in generale, a proposito dei pericoli dell'undefined behavior) consiglio di dare un'occhiata a qualche articolo:
http://blog.llvm.org/2011/05/what-ev...ould-know.html
http://blog.llvm.org/2011/05/what-ev...d-know_14.html
http://blog.llvm.org/2011/05/what-ev...d-know_21.html
(più magari questa serie come introduzione:
http://blog.regehr.org/archives/213
http://blog.regehr.org/archives/226
http://blog.regehr.org/archives/232)
Ultima modifica di MItaly; 13-10-2014 a 01:07
Amaro C++, il gusto pieno dell'undefined behavior.
Si ma la la cosa eclatante è che test non è static, per cui necessita di un istanza per essere richiamato e qui invece pure null va bene ... dunque verificare prima se è diverso da null e poi richiamare il metodo è corretto.
Con i sogni possiamo conoscere il futuro...
Nominalmente gli hai dato un puntatore ad un'istanza - che poi non sia un'istanza valida (e che per casi strani non faccia una piega neanche a runtime) è un altro paio di maniche.
Sì; in alternativa, come detto sopra, puoi sollevare un'eccezione (che risale lo stack e, tra le altre cose, evita che venga eseguita la chiamata al metodo).dunque verificare prima se è diverso da null e poi richiamare il metodo è corretto.
Amaro C++, il gusto pieno dell'undefined behavior.
Posso capirlo a compile time perchè non da errore ma non capisco proprio perchè non da errore a run-time.
Inoltre approfitto per chiederti questa cosa.
Ho il file Entity.h:
come puoi notare ho implementato direttamente ne file header i metodi addComponent e getComponent, e l'ho dovuto fare dato che se li implemento in Entity.cpp mi rileva questo errore:codice:#include <string> #include <map> #include <iostream> #include <typeindex> #include "IComponent.h" using namespace std; class Entity { public: template <class T> void addComponent(){ T *newComponent = new T(); this->mComponents[type_index(typeid(T))] = newComponent; //this->mComponents.insert(pair<type_index, T*>(type_index(typeid(T)), newComponent)); newComponent->entity = this; newComponent->init(); } template <class T> T* getComponent(){ map<type_index, IComponent*>::iterator it = this->mComponents.find(type_index(typeid(T))); if (it != this->mComponents.end()) return static_cast<T*>(it->second); else return NULL; } void removeComponent(string name, IComponent *Component); bool HasComponent(int eID); //void fetchComponents(); map<type_index, IComponent*> mComponents; private: // Entity ID int eID; // riferimento a tutti i componenti //map<type_index, IComponent*> mComponents; };
codice:riferimento al simbolo esterno "public: class AI_followTarget * __thiscall Entity::getComponent<class AI_followTarget>(void)" (??$getComponent@VAI_followTarget@@@Entity@@QAEPAVAI_followTarget@@XZ) non risolto nella funzione _main
Con i sogni possiamo conoscere il futuro...
Perché a runtime non viene fatto alcun controllo sulla validità dell'istanza! Il tuo metodo non accede a nulla dei campi dell'istanza su cui l'hai richiamato, per cui non si accorge neanche del fatto che è stato richiamato su un NULL. Se tu in quel metodo invece avessi provato a leggere/scrivere i campi della classe, allora con ogni probabilità avresti ottenuto un crash. Again: è undefined behavior sostanzialmente per motivi di prestazioni, un po' come il fatto che non c'è alcun controllo sullo sforamento dagli array, sull'overflow di interi, eccetera.
Le funzioni/i metodi template devono essere definiti sempre nei .h, dato che ogni volta che vengono istanziati è necessario che il compilatore ne conosca i sorgenti (a differenza di quanto accade per le funzioni "normali", di cui è sufficiente che ci sia il prototipo, che poi viene collegato alla definizione vera e propria in fase di linking).Inoltre approfitto per chiederti questa cosa.
Leggi qui per ulteriori dettagli.
Amaro C++, il gusto pieno dell'undefined behavior.
Perfetto ora mi è più chiaro. Dunque non gli interessa nemmeno se è un metodo d'istanza affinche non usa un membro della classe.
Riguardo alla definizione dei metodi template. Se in .h faccio semplicemente:
Template <class T> void addComponent() .. e implemento il metodo in Entity.cpp non mi da problemi a differenza di getComponent (come puoi vedere dall'errore). Come mai?
Con i sogni possiamo conoscere il futuro...
Esatto; ricordati comunque che, anche se in questo caso "per caso" funziona, è comunque un errore nel tuo codice chiamare metodi su istanze non valide.
Perché non l'hai richiamato da altri .cpp.Riguardo alla definizione dei metodi template. Se in .h faccio semplicemente:
Template <class T> void addComponent() .. e implemento il metodo in Entity.cpp non mi da problemi a differenza di getComponent (come puoi vedere dall'errore). Come mai?
Amaro C++, il gusto pieno dell'undefined behavior.