View Full Version : [c++] problema con std::string che risulta illeggibile
Sera a tutti, ho un problema piuttosto strano in un metodo che ho scritto.
Il problema è che la stringa (restituita tramite il metodo c_str() come const char), risulta in un insieme di caratteri illeggibili.
Ma prima di andare avanti ecco la definizione del metodo:
const char *Resources::getImage(const char *resourceID) {
TiXmlNode *imagesNode = this->resourceFile.FirstChild("Images");
TiXmlElement *resourceImage;
for (resourceImage = imagesNode->FirstChildElement("Image");
resourceImage != NULL;
resourceImage = imagesNode->NextSiblingElement())
{
if (strcmp(resourceImage->Attribute("identifier"), resourceID) == 0) {
if(resourceImage->Attribute("path")) {
// DA QUI LA STRINGA ILLEGGIBILE, resourcePath
std::string resourcePath;
resourcePath.append(this->resourcesRoot);
resourcePath.append(std::string(resourceImage->Attribute("path")));
return resourcePath.c_str();
}
else {
std::string errorMessage("Missing reference for: ");
errorMessage.append(resourceID);
Engine::instance()->fatalError(errorMessage.c_str());
}
}
else {
std::string alertMessage("Cannot get the resource reference to: ");
alertMessage.append(resourceID);
Engine::instance()->fatalError(alertMessage.c_str());
}
}
return NULL;
}
Come si può intuire, questo metodo dovrebbe cercare in un file xml (gestito attraverso la libreria TinyXML.
Il problema è rappresentato da resourcePath che è una stringa assemblata con this->resourcesRoot che è una stringa contenente una porzione di path, e il contenuto dell'attributo "path" dell'elemento, che viene restituito sotto forma di const char *.
Se provo a stampare (per debug) da dentro la funzione, o a restituire il singolo this->resourcesRoot o resourceImage->Attribute("path") questi sono perfettamente leggibili e funzionanti, ma in questo punto:
// DA QUI LA STRINGA ILLEGGIBILE, resourcePath
std::string resourcePath;
resourcePath.append(this->resourcesRoot);
resourcePath.append(std::string(resourceImage->Attribute("path")));
return resourcePath.c_str();
Si verifica il mistero, per completezza inserisco una immagine che mostra a schermo il risultato ottenuto (la scritta dopo è una informazione di debug, non ha alcuna attinenza con il metodo in questione):
http://img267.imageshack.us/img267/6138/erroreyo.png
Ringrazio in anticipo chiunque si interessi.
...
Il puntatore che ritorni col metodo c_str() è invalidato nel momento in cui la stringa viene deallocata (cioè all'atto del return).
Se non hai la possibilità di ritornare una std::string, puoi allocare dinamicamente una stringa C, copiarvi il contenuto della tua std::string usando strcpy e il metodo c_str() e dunque ritornare un puntatore a questa.
Il puntatore che ritorni col metodo c_str() è invalidato nel momento in cui la stringa viene deallocata (cioè all'atto del return).
Se non hai la possibilità di ritornare una std::string, puoi allocare dinamicamente una stringa C, copiarvi il contenuto della tua std::string usando strcpy e il metodo c_str() e dunque ritornare un puntatore a questa.
Caspita che problema tedioso :mbe:+
Purtroppo non ho possibilità di restituire direttamente una stringa in quanto il risultato è utilizzato come parametro per altre funzioni C, comunque la soluzione che mi hai proposto ha funzionato alla grande :)
Mi ero dimenticato dello scope della variabile, e sinceramente, pensavo che il risultato di c_str() fosse permanente, non dipendente dalla classe.
Grazie mille, mi hai risparmiato un'altra giornata di grattacapi :D
Caspita che problema tedioso :mbe:+
Purtroppo non ho possibilità di restituire direttamente una stringa in quanto il risultato è utilizzato come parametro per altre funzioni C, comunque la soluzione che mi hai proposto ha funzionato alla grande :)
Mi ero dimenticato dello scope della variabile, e sinceramente, pensavo che il risultato di c_str() fosse permanente, non dipendente dalla classe.
Grazie mille, mi hai risparmiato un'altra giornata di grattacapi :D
No, anzi, il puntatore restituito da c_str() è invalidato anche all'interno dello scope se la std::string viene modificata (almeno "da standard", se poi risulti ancora utilizzabile in alcuni casi non lo so, ma non bisogna farci affidamento).
Se è necessario riutilizzarne il contenuto, bisogna per forza copiarlo.
tomminno
25-07-2010, 15:05
Caspita che problema tedioso :mbe:+
Purtroppo non ho possibilità di restituire direttamente una stringa in quanto il risultato è utilizzato come parametro per altre funzioni C, comunque la soluzione che mi hai proposto ha funzionato alla grande :)
Ricordati che poi devi deallocare l'array!
Se la utilizzi così hai un memory leak:
Func(1,2,getImage("abc"));
sempre che Func non deallochi l'array, che è comunque molto pericoloso, perchè il giorno che devi chiamare:
const char * img = getImage("abc");
Func(1,2,img);
Func2(3,4,img);
sei comunque fregato.
Chissà perchè il C++ venga sempre utilizzato come C con le classi...
Ricordati che poi devi deallocare l'array!
Se la utilizzi così hai un memory leak:
Func(1,2,getImage("abc"));
sempre che Func non deallochi l'array, che è comunque molto pericoloso, perchè il giorno che devi chiamare:
const char * img = getImage("abc");
Func(1,2,img);
Func2(3,4,img);
sei comunque fregato.
Chissà perchè il C++ venga sempre utilizzato come C con le classi...
Beh, tante volte la colpa non è dell'utente ma del dover far convivere più librerie, di cui magari alcune disponibili solo in C.
Ovviamente la necessità di deallocare esternamente c'è, non ne ho parlato perché incoraggio fortemente altre soluzioni (basti pensare che avere una allocazione nel tuo metodo, e una deallocazione a cura dell'utente del metodo, è rischiosissimo in ottica "black box" e renderebbe il tutto decisamente poco usabile da un ipotetico user della tua classe, senza considerare quanto si presti a tue distrazioni e leaks casalinghi).
Ti incoraggio fortemente a ritornare una std::string, e dunque a realizzare un wrapper per il tuo metodo che accetta solo char* (o, semplicemente, a passargliela tramite c_str(): in questo caso, e ammesso che tale metodo non debba modificare la stringa, sei sicuro della validità del puntatore per tutta la durata del metodo!).
Un'altra alternativa può essere ritornare una classe gestore della risorsa che incapsuli un char* e si occupi, oltre che di metterlo a disposizione del metodo chiamante, anche della sua deallocazione all'uscita dallo scope: sinceramente il gioco non vale la candela ;)
No, anzi, il puntatore restituito da c_str() è invalidato anche all'interno dello scope se la std::string viene modificata (almeno "da standard", se poi risulti ancora utilizzabile in alcuni casi non lo so, ma non bisogna farci affidamento).
Se è necessario riutilizzarne il contenuto, bisogna per forza copiarlo.
Capito, beh ti ringrazio della precisazione, mi sarà sicuramente utile per il futuro :)
Ricordati che poi devi deallocare l'array! Ci avevo pensato, ma non ho trovato alcuna soluzione fruibile, poichè l'output viene dato in pasto alle funzioni di una libreria esterna, la maggior parte delle volte, infatti ci stavo proprio pensando, comunque stò implementando un sistema per vedere se la risorsa da caricare è già stata richiesta, e nel caso non fare nuovamente la trafila ma restituire direttamente path (una sorta di hash table per tenere traccia del risultato, in questo modo non ci sono dispersioni di memoria).
Se la utilizzi così hai un memory leak:
Func(1,2,getImage("abc"));
sempre che Func non deallochi l'array, che è comunque molto pericoloso, perchè il giorno che devi chiamare:
const char * img = getImage("abc");
Func(1,2,img);
Func2(3,4,img);
sei comunque fregato.
Chissà perchè il C++ venga sempre utilizzato come C con le classi...
Hai qualche soluzione da consigliare?
Hai qualche soluzione da consigliare?
È importante capire se la libreria esterna deve o meno modificare la stringa che riceve... se nel prototipo vedi che è qualcosa del tipo:
funzioneEsterna(const char* s);
il tuo metodo potrebbe tranquillamente ritornare una std::string, e passarla come:
funzioneEsterna(mioMetodo().to_str());
È importante capire se la libreria esterna deve o meno modificare la stringa che riceve... se nel prototipo vedi che è qualcosa del tipo:
funzioneEsterna(const char* s);
il tuo metodo potrebbe tranquillamente ritornare una std::string, e passarla come:
funzioneEsterna(mioMetodo().to_str());
Caspita, quello che mi fai notare non è affatto sbagliato.
Almeno per le funzioni che accettano il risultato di getImage, i prototipi sono tutti o quasi const char *filename, visto che devono solo occuparsi di caricare le risorse, non di modificare il nome.
Mi sà che agirò in questo modo.
Grazie ancora una volta :)
Caspita, quello che mi fai notare non è affatto sbagliato.
Almeno per le funzioni che accettano il risultato di getImage, i prototipi sono tutti o quasi const char *filename, visto che devono solo occuparsi di caricare le risorse, non di modificare il nome.
Mi sà che agirò in questo modo.
Grazie ancora una volta :)
Un'alternativa è mantenere il puntatore con uno scoped_array (http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/scoped_array.htm), ma mi sembra che l'altra soluzione sia più comoda :D
Un'alternativa è mantenere il puntatore con uno scoped_array (http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/scoped_array.htm), ma mi sembra che l'altra soluzione sia più comoda :D
Ehm si decisamente più comoda la precendente :D
E poi adottare questa soluzione vorrebbe dire aggiungere un'altra libreria, ed essendo la precedente una soluzione funzionante, mi stà bene anche evitare di aggiungere altro :)
Comunque hai fatto benissimo a linkare la risorsa, molto utile :)
Ciao.
Caspita che problema tedioso :mbe:+
Purtroppo non ho possibilità di restituire direttamente una stringa in quanto il risultato è utilizzato come parametro per altre funzioni C, comunque la soluzione che mi hai proposto ha funzionato alla grande :)
Sinceramente non mi piace molto allocare una risorsa dentro ad una funzione e poi dover imporre al chiamante la deallocazione.
Le soluzioni che ti posso proporre sono queste:
std::string myFunction(...)
{
std::string s;
...
return s;
}
otherFunction(myFunction(...).c_str(), ...);
char * myFunction(char * buffer, ...)
{
std::string s;
...
strncpy(buffer, s.c_str(), MAX_PATH);
return buffer;
}
char buffer[MAX_PATH + 1];
otherFunction(myFunction(buffer, ...), ...);
DanieleC88
27-07-2010, 10:13
std::string myFunction(char * buffer, ...)
{
std::string s;
...
strncpy(buffer, s.c_str(), MAX_PATH);
return buffer;
}
:wtf:
DanieleC88
27-07-2010, 10:22
Toh, funziona! :D
Crea l'oggetto std::string corrispondente a partire dal contenuto di buffer? Perché se è così a quel punto conviene usare direttamente std::string, no? :)
ciao ;)
Toh, funziona! :D
Crea l'oggetto std::string corrispondente a partire dal contenuto di buffer? Perché se è così a quel punto conviene usare direttamente std::string, no? :)
ciao ;)
Era un errore, non avevo messo il valore di ritorno corretto :D
DanieleC88
27-07-2010, 10:33
E infatti così mi pareva, ma mi compila e funziona pure! :eek:
(Almeno, con clang, ora provo gcc...)
E infatti così mi pareva, ma mi compila e funziona pure! :eek:
Che funzionasse era ovvio, visto che std::string ha i costruttori adatti per fare quella conversione ;)
DanieleC88
27-07-2010, 10:40
Be' quello sì, ma non ero certo che il compilatore facesse implicitamente la costruzione dell'oggetto senza lamentarsi.
Beata ignoranza. :p
In teoria, con il passaggio del buffer, la cosa più "pulita" sarebbe passare anche la dimensione del buffer stesso.
Scusa se ti rispondo con ritardo, potrei inventare mille scuse per giustificarmi, ma la verità è che mi sono scordato di seguire questo topic.
Sinceramente non mi piace molto allocare una risorsa dentro ad una funzione e poi dover imporre al chiamante la deallocazione. Infatti, non piace nemmeno a me, anche perchè non sono l'unico a dover usare quelle funzioni, e se qualcuno non legge bene la documentazione ecco che il prodotto finale inizia ad avere problemi.
Le soluzioni che ti posso proporre sono queste:
std::string myFunction(...)
{
std::string s;
...
return s;
}
otherFunction(myFunction(...).c_str(), ...);
Questa mi sembra una buona soluzione, alla fine un .c_str() in più non fà alcun male.
Mi devo ancora abituare all'ottica OOP, e a tutte le features connesse :fagiano:
char * myFunction(char * buffer, ...)
{
std::string s;
...
strncpy(buffer, s.c_str(), MAX_PATH);
return buffer;
}
char buffer[MAX_PATH + 1];
otherFunction(myFunction(buffer, ...), ...);
Uhm, a questa sinceramente non ci avevo mai pensato, cioè al massimo passare buffer, poi chiamare myFunction(buffer, ...) e quindi in otherFunction(buffer). Arguta questa soluzione :)
Devo annotarmela.
Ti ringrazio per i consigli, ho fatto un paio di modifiche e anzichè dover restituire il path della risorsa, la carico direttamente e restituisco agli handler una risorsa già pronta anzichè farla caricare dagli handler stessi.
In questo modo ho risolto il problema della stringa, anche se adesso devo trovare un modo di gestire la persistenza di questi oggetti :s
Se non si fosse capito (e può essere visto che poco o nulla lo lascia intendere) stò progettando un sistema di GUI, per chiarezza usando OpenGL ed SDL.
Il secondo sistema è praticamente quello usato dal 90% delle API Win32 ;)
Il secondo sistema è praticamente quello usato dal 90% delle API Win32 ;)
Beh, allora buono a sapersi :)
E' la seconda volta in questa settimana che Microsoft mi stupisce (sarà il periodo, magari sono io più facilmente impressionabile).
Grazie ancora! :D
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.