[C++] Zwischenspeichern eines Const Char * als Rückgabetyp


Answers

Ich mag alle Antworten darüber, wie die Zeichenfolge statisch zugewiesen werden kann, aber das gilt nicht unbedingt für alle Implementierungen, insbesondere für diejenige, deren Dokumentation das ursprüngliche Poster verknüpft hat. In diesem Fall scheint der Name des dekorierten Typs statisch gespeichert zu werden, um Speicherplatz zu sparen, und der Name des undekorierten Typs wird bei Bedarf berechnet und in einer verknüpften Liste zwischengespeichert.

Wenn Sie neugierig sind, wie die Implementierung von Visual C ++ type_info::name() ihren Speicher type_info::name() und zwischenspeichert, ist es nicht schwer herauszufinden. Erstellen Sie zunächst ein kleines Testprogramm:

#include <cstdio>
#include <typeinfo>
#include <vector>    
int main(int argc, char* argv[]) {
    std::vector<int> v;
    const type_info& ti = typeid(v);
    const char* n = ti.name();
    printf("%s\n", n);
    return 0;
}

Erstellen Sie es und führen Sie es unter einem Debugger aus (ich habe WinDbg verwendet) und schauen Sie sich den Zeiger an, der von type_info::name() . Zeigt es eine globale Struktur? Wenn dies der Fall ist, wird der Befehl In von WinDbg den Namen des nächsten Symbols angeben:

0:000> ?? n
char * 0x00000000`00857290
 "class std::vector<int,class std::allocator<int> >"
0:000> ln 0x00000000`00857290
0:000>

ln wurde nichts gedruckt, was darauf hinweist, dass die Zeichenfolge nicht im Adressbereich eines bestimmten Moduls lag. Es wäre in diesem Bereich, wenn es sich um das Daten- oder das Nur-Lese-Datensegment handelte. Lassen Sie uns sehen, ob es auf dem Heap zugeordnet wurde, indem Sie alle Heaps nach der von type_info::name() Adresse type_info::name() :

0:000> !heap -x 0x00000000`00857290
Entry             User              Heap              Segment               Size  PrevSize  Unused    Flags
-------------------------------------------------------------------------------------------------------------
0000000000857280  0000000000857290  0000000000850000  0000000000850000        70        40        3e  busy extra fill 

Ja, es wurde auf dem Heap zugewiesen. Das Setzen eines Haltepunkts am Start von malloc() und das Neustarten des Programms bestätigen dies.

Ein Blick auf die Deklaration in <typeinfo> gibt einen Hinweis darauf, wo die Heap-Zeiger zwischengespeichert werden:

struct __type_info_node {
    void *memPtr;
    __type_info_node* next;
};

extern __type_info_node __type_info_root_node;
...
_CRTIMP_PURE const char* __CLR_OR_THIS_CALL name(__type_info_node* __ptype_info_node = &__type_info_root_node) const;

Wenn Sie die Adresse von __type_info_root_node und die Liste im Debugger __type_info_root_node , finden Sie schnell einen Knoten, der dieselbe Adresse enthält, die von type_info::name() . Die Liste scheint mit dem Caching-Schema verwandt zu sein.

Die MSDN-Seite, die in der ursprünglichen Frage verknüpft wird, scheint die Leerzeichen zu füllen: der Name wird in seiner dekorierten Form gespeichert, um Speicherplatz zu sparen, und auf dieses Formular kann über type_info::raw_name() . Wenn Sie type_info::name() zum ersten Mal für einen bestimmten Typ aufrufen, type_info::name() er den Namen, speichert ihn in einem Puffer, der dem Heap zugewiesen wurde, speichert den Pufferzeiger und gibt ihn zurück.

Die verknüpfte Liste kann auch verwendet werden, um die zwischengespeicherten Zeichenfolgen während des Programm-Exits freizugeben (ich habe jedoch nicht überprüft, ob dies der Fall ist). Dadurch wird sichergestellt, dass sie beim Ausführen eines Speicherdebuggtools nicht als Speicherlecks angezeigt werden.

Question

Ich habe etwas über C ++ gelesen und diesen Artikel über RTTI (Runtime Type Identification) gefunden: http://msdn.microsoft.com/en-us/library/70ky2y6k(VS.80).aspx . Nun, das ist ein anderes Thema :) - Ich stolperte jedoch über ein seltsames Sprichwort in der type_info Klasse, nämlich über die ::name Methode. Darin heißt es: "Die type_info::name gibt ein const char* an eine mit Null abgeschlossene Zeichenfolge zurück, die den lesbaren Namen des Typs darstellt. Der Speicher, auf den verwiesen wird, wird zwischengespeichert und sollte niemals direkt freigegeben werden."

Wie kannst du so etwas selbst umsetzen? Ich habe mich schon oft mit genau diesem Problem herumgeschlagen, weil ich kein neues char Array für den Aufrufer erstellen möchte, um es zu löschen, also bin ich bis jetzt bei st std::string .

Lassen Sie uns der Einfachheit halber sagen, dass ich eine Methode erstellen möchte, die "Hello World!" nennen wir es

const char *getHelloString() const;

Persönlich würde ich es irgendwie so machen (Pseudo):

const char *getHelloString() const
{
  char *returnVal = new char[13];
  strcpy("HelloWorld!", returnVal);

  return returnVal
}

.. Aber das würde bedeuten, dass der Aufrufer eine delete[] auf meinem Rückkehrzeiger machen sollte :(

Danke im Voraus




Ich denke, so etwas kann nur "sauber" mit Objekten und dem RAII-Idiom umgesetzt werden. Wenn der Objects-Destruktor aufgerufen wird (obj den Geltungsbereich verlässt), können wir sicher annehmen, dass die const char* -Zeiger nicht mehr verwendet werden.

Beispielcode:

class ICanReturnConstChars
{
    std::stack<char*> cached_strings
    public:
    const char* yeahGiveItToMe(){
        char* newmem = new char[something];
        //write something to newmem
        cached_strings.push_back(newmem);
        return newmem;
    }
    ~ICanReturnConstChars(){
        while(!cached_strings.empty()){
            delete [] cached_strings.back()
            cached_strings.pop_back()
        }
    }
};

Die einzige andere Möglichkeit, die ich kenne, besteht darin, einen smart_ptr zu übergeben.




Sie können sich nicht auf GC verlassen; Das ist C ++. Das bedeutet, dass Sie den Speicher verfügbar halten müssen, bis das Programm beendet wird. Sie wissen einfach nicht, wann es sicher ist, [] es zu löschen. Also, wenn Sie ein const Char * konstruieren und zurückgeben wollen, einfach neu [] und es zurückgeben. Akzeptiere das unvermeidbare Leck.




Warum muss der Rückgabetyp const ? Denken Sie nicht an die Methode als eine Get- Methode, denken Sie an sie als eine Create- Methode. Ich habe viele APIs gesehen, die erfordern, dass Sie etwas löschen, das ein Erstellungsoperator / eine Methode zurückgibt. Achten Sie darauf, dass Sie das in der Dokumentation beachten.

/* create a hello string
 * must be deleted after use
 */
char *createHelloString() const
{
  char *returnVal = new char[13];
  strcpy("HelloWorld!", returnVal);

  return returnVal
}



So etwas würde tun:

const char *myfunction() {
    static char *str = NULL; /* this only happens once */
    delete [] str; /* delete previous cached version */
    str = new char[strlen("whatever") + 1]; /* allocate space for the string and it's NUL terminator */
    strcpy(str, "whatever");
    return str;
}

EDIT: Etwas, das mir auffiel, ist, dass ein guter Ersatz dafür ein boost :: shared_pointer sein könnte. Auf diese Weise kann der Anrufer sie so lange speichern, wie sie möchten, und sie müssen sich keine Gedanken darüber machen, sie explizit zu löschen. Ein fairer Kompromiss IMO.




Ich denke, da sie wissen, dass es eine endliche Anzahl von ihnen gibt, halten sie sie einfach für immer herum. Es mag in einigen Fällen angebracht sein, das zu tun, aber in der Regel wird std :: string besser sein.

Sie können auch nach neuen Aufrufen suchen, um zu sehen, ob sie diese Zeichenfolge bereits erstellt haben und denselben Zeiger zurückgeben. Abhängig davon, was Sie tun, kann dies auch für Sie nützlich sein.




Links