hmm: C++: char* behält zugewiesenen wert und std::string nimmt default an... wieso?

Hi Leute,

bei meinen Versuchen eine C++ Bibliothek per JavaScript zu visualisieren bin ich auf folgendes gestoßen:

class Bla
{
  public:
    static std::string a;
    static std::char* b;
    ~Bla()
    {
      printf(a);
      printf(b);
    }
} Bla;

std::string Bla::a = "";
std::char* Bla::b = "";

irgendEinKlasse::FunktionDieAufgerufenWird()
{
  Bla::a = "test";
  Bla::b = "test";
}

wenn ich das mache so ist im Destruktor ~Bla a="" und b="test".

woran liegt das?

wie kann ich dafür sorgen, dass std::string a im Destruktor von Bla den Wert behält den es von irgendEinKlasse::FunktionDieAufgerufenWird() bekommen hat? Mein Ziel wäre es, später in irgendEinKlasse::FunktionDieAufgerufenWird() anstelle eine std::string ein eigenes kompliziertes datenobjekt zu setzen und dass dann im destruktor abzufragen... aber dafür muss ich erstmal verstehen wie ich machen kann das std::string a seinen wert behält.

Ideen und/oder Erklärungen?

akzeptierte Antworten

  1. Hallo @hmm,

    class Bla
    {
      public:
        static std::string a;
        static std::char* b;
        ~Bla()
        {
          printf(a);
          printf(b);
        }
    } Bla;
    
    • std::char brauchst du nicht, char reicht. (War mir neu, dass das überhaupt funktioniert.)
    • Was macht denn das Bla nach dem Ende der Klassendefinition?
    • Welchen Zweck hat die Klasse, wenn es keine sinnvollen Objekte davon gibt? Deine static-Attribute sind Klassenattribute, keine Klassenattribute.
    • Du verwendest printf in einer unsicheren Variante: Sobald a oder b einen Formatstring enthalten, greifst du auf undefinierten Speicher zu. Nimm lieber cout oder puts.
    • Warum definiert die Klasse als einzige Methode den Destruktor?
    std::string Bla::a = "";
    std::char* Bla::b = "";
    
    irgendEinKlasse::FunktionDieAufgerufenWird()
    {
      Bla::a = "test";
      Bla::b = "test";
    }
    

    wenn ich das mache so ist im Destruktor ~Bla a="" und b="test".

    woran liegt das?

    Ich vermute, dass von a der Destruktor aufgerufen wird. b ist als Pointer auf einen char definiert, also „herkömmliches C“, da funktioniert die Speicherverwaltung anders.

    Mein Ziel wäre es, später in irgendEinKlasse::FunktionDieAufgerufenWird() anstelle eine std::string ein eigenes kompliziertes datenobjekt zu setzen und dass dann im destruktor abzufragen... aber dafür muss ich erstmal verstehen wie ich machen kann das std::string a seinen wert behält.

    Dann schau dir mal, wie ein Objekt mit seinen Attributen „zerstört“ wird, sprich in welcher Reihenfolge die Destruktoren aufgerufen werden.

    Ein std::string ist übrigens auch ein „kompliziertes Datenobjekt“ ;)

    Viele Grüße
    Robert

    1. Hi danke. das irgendein Destruktor die Daten weghaut kann sehr gut sein.

      Mein kompliziertes Daten-Objekt sieht so aus:

      typedef std::map<std::string, int> StateCounter;
      typedef std::vector<StateCounter> StateCounterVector;
      struct TransitionCounter
      {
      	StateCounterVector stateCounters;
      };
      typedef std::map<std::string, TransitionCounter> StimulationToTransitionMap;
      

      Eine solche StimmulationToTransitionMap möchte ich in ~Bla verwenden. Wie schaffe ich das?

      Ich weiß leider nicht wann irgendEinKlasse::FunktionDieAufgerufenWird() das letzte mal aufgerufen wird und irgendEinKlasse wird sehr häufig erstellt und wieder vernichtet, jedesmal mit mehreren Funktionsaufrufen.

      Was macht denn das Bla nach dem Ende der Klassendefinition?

      Es soll die Daten der StimulationToTransitionMap aufbereiten und in ein File schreiben.

      Welchen Zweck hat die Klasse, wenn es keine sinnvollen Objekte davon gibt? Deine static-Attribute sind Klassenattribute, keine Klassenattribute.

      Ich brauche das Objekt um das Ende des Programms zu finden. Das liegt daran, dass irgendEinKlasse immer wieder gebaut und zerlegt wird und jedesmal die FunktionDieAufgerufenWird ausgelöst wird. Die FunktionDieAufgerufenWird schreibt man dann jedesmal weitere Daten in meine static Map.

      Warum definiert die Klasse als einzige Methode den Destruktor?

      oben.

      1. Hallo @hmm,

        Mein kompliziertes Daten-Objekt sieht so aus:

        typedef std::map<std::string, int> StateCounter;
        typedef std::vector<StateCounter> StateCounterVector;
        struct TransitionCounter
        {
        	StateCounterVector stateCounters;
        };
        

        Fehlt da noch etwas oder ist geplant diese struct zu erweitern?

        Eine solche StimmulationToTransitionMap möchte ich in ~Bla verwenden. Wie schaffe ich das?

        Ich weiß leider nicht wann irgendEinKlasse::FunktionDieAufgerufenWird() das letzte mal aufgerufen wird und irgendEinKlasse wird sehr häufig erstellt und wieder vernichtet, jedesmal mit mehreren Funktionsaufrufen.

        Beschreib mal bitte genauer, was du vorhast. Aus der bisherigen Problembeschreibung werde ich nicht schlau. Mir scheint ein konzeptionelles Problem vorzuliegen.

        Was macht denn das Bla nach dem Ende der Klassendefinition? Es soll die Daten der StimulationToTransitionMap aufbereiten und in ein File schreiben.

        Nein, ich meinte was das Bla – und nicht das ~Bla – nach dem Ende der Klassendefinition in deinem Quellcode macht, also warum

        class Bla {} Bla;
        

        statt

        class Bla {};
        

        Welchen Zweck hat die Klasse, wenn es keine sinnvollen Objekte davon gibt? Deine static-Attribute sind Klassenattribute, keine Klassenattribute. Ich brauche das Objekt um das Ende des Programms zu finden. Das liegt daran, dass irgendEinKlasse immer wieder gebaut und zerlegt wird und jedesmal die FunktionDieAufgerufenWird ausgelöst wird. Die FunktionDieAufgerufenWird schreibt man dann jedesmal weitere Daten in meine static Map.

        Irgendwann verlässt dein Programm doch main, warum nutzt du nicht das?

        Warum definiert die Klasse als einzige Methode den Destruktor? oben.

        Ein konzeptionelles Problem, siehe oben.

        Viele Grüße
        Robert

        1. Die struc wird später erweitert.

          Wenn ich kein bla; am Ende der Definition reinsetze sagt mir meine IDE:

          fehler: return type may not be specified on a destructor

          Mein Problem ist folgendes:

          Das Programm erzeugt Objekte von irgendEinKlasse und zerstört diese wieder. Das muss ich Dokumentieren. Das tue ich derzeit dadurch, dass ich im Destruktor von irgendEinKlasse meine static Map erweitere. Jetzt möchte ich u.a. die Durchschnittliche Anzahl an Aufrufen von irgendEinKlasse::FunktionDieAufgerufenWird() über alle Objektinstancen von irgendEinKlasse berechnen und das ausgeben.

          Ich kann den Aufrufenden Code leider nicht modifizieren und ich weiß nicht wann die letzte Instancz gebaut bzw. zerschlagen wird. Aus diesem Grund baue ich die Klasse bla welche genau dann zerstört wird, wenn das Programm beendet wird, also dann wenn alle Instanzen von irgendEinKlasse zerschlagen wurden also genau dann wenn ich alle erforderlichen Daten in meiner static map habe.

          wäre meine static map ein int so würde ich alle erforderlichen daten in ~bla haben… leider würde es zuviel laufzeit fressen diie map daten regelmäßig in ein txt file zu schreiben...

          1. Hallo @hmm,

            Wenn ich kein bla; am Ende der Definition reinsetze sagt mir meine IDE:

            fehler: return type may not be specified on a destructor

            Was für eine IDE ist das denn? Denn der Destruktor hat auch in deinem Fall ja gar keinen Rückgabetyp.
            Wenn ich den GCC verwende, stößt der sich an etwas Anderem: Er mag std::char nicht.

            Das Programm erzeugt Objekte von irgendEinKlasse und zerstört diese wieder. Das muss ich Dokumentieren. Das tue ich derzeit dadurch, dass ich im Destruktor von irgendEinKlasse meine static Map erweitere.

            Das sollte soweit auch funktionieren, oder?

            Jetzt möchte ich u.a. die Durchschnittliche Anzahl an Aufrufen von irgendEinKlasse::FunktionDieAufgerufenWird() über alle Objektinstancen von irgendEinKlasse berechnen und das ausgeben.

            Genau, dafür brauchst du die Anzahl aller Instanzen, die du erst am Ende hast.

            Ich kann den Aufrufenden Code leider nicht modifizieren und ich weiß nicht wann die letzte Instancz gebaut bzw. zerschlagen wird. Aus diesem Grund baue ich die Klasse bla welche genau dann zerstört wird, wenn das Programm beendet wird, …

            Vielleicht hilft dir ja die Funktion atexit weiter.

            wäre meine static map ein int so würde ich alle erforderlichen daten in ~bla haben… leider würde es zuviel laufzeit fressen diie map daten regelmäßig in ein txt file zu schreiben …

            Um welche Datenmengen handelt es sich denn? Und die könnten ja auch asynchron, d.h. in einem zweiten Thread, geschrieben werden.

            Viele Grüße
            Robert

            1. Das sollte soweit auch funktionieren, oder?

              tut es

              Vielleicht hilft dir ja die Funktion atexit weiter.

              und wo bau ich das ein? ich kann leider nur bla und irgendEinKlasse becoden oder neue klassen machen

              Um welche Datenmengen handelt es sich denn? Und die könnten ja auch asynchron, d.h. in einem zweiten Thread, geschrieben werden.

              7.000 mal ca wird irgendEinKlasse gebaut.

              gibt es gar keine möglichkeit die map so umzuschreiben das der ~bla sie noch hat?

              ps: ich hasse c++ aus tiefster seele.... und mir wird gerad wieder klar warum....

              1. Hallo @hmm,

                Vielleicht hilft dir ja die Funktion atexit weiter. und wo bau ich das ein?

                Wo Platz ist. Die Funktion muss ja nur einmal aufgerufen werden um den Handler zu installieren.

                Um welche Datenmengen handelt es sich denn? Und die könnten ja auch asynchron, d.h. in einem zweiten Thread, geschrieben werden.

                7.000 mal ca wird irgendEinKlasse gebaut.

                Wie lange läuft das Programm? Was macht irgendEineKlasse? Ich vermute weiterhin, dass es da ein konzeptionelles Problem gibt.

                gibt es gar keine möglichkeit die map so umzuschreiben das der ~bla sie noch hat?

                Da ich deinen Code nicht hinreichend genau kenne, kann ich dir diese Frage nicht beantworten. Kannst du mehr Details geben?

                ps: ich hasse c++ aus tiefster seele.... und mir wird gerad wieder klar warum....

                Da sind übrigens noch Fragen offen.

                Viele Grüße
                Robert

                1. Wie lange läuft das Programm? Was macht irgendEineKlasse? Ich vermute weiterhin, dass es da ein konzeptionelles Problem gibt.

                  aktuell nur 20 Sekunden oder 30 und die Klasse tut nur die folgenden drei Dinge:

                  • macht im Konstruktor das static StateCounter Objekt leer.

                  • füllt den statecounter mit Key EreignisName und einem Counter, das funktioniert per: stateCounter[EreignisName]++ und das sammle ich während der Lebenszeit des Objekts in einem Vector dem StateCounterVector

                  • im Destruktor tue ich das dann in meine static map per stimulationToTransitionMap[stimulationName]=TransitionCounter;

                  Da ich deinen Code nicht hinreichend genau kenne, kann ich dir diese Frage nicht beantworten. Kannst du mehr Details geben?

                  hab ich das gerade ausreichend gut formuliert?

                  Da sind übrigens noch Fragen offen.

                  visual studios. in meinem code steht übrigens nur char*, std::char+ war nen schreibfehler

                  1. Moin @hmm,

                    Wie lange läuft das Programm? Was macht irgendEineKlasse? Ich vermute weiterhin, dass es da ein konzeptionelles Problem gibt.

                    aktuell nur 20 Sekunden oder 30

                    Du erzeugst und vernichtest ein paar hundert Objekte pro Sekunde?

                    Da ich deinen Code nicht hinreichend genau kenne, kann ich dir diese Frage nicht beantworten. Kannst du mehr Details geben?

                    hab ich das gerade ausreichend gut formuliert?

                    Ich habe nicht umsonst den Begriff Code verwendet.

                    Da sind übrigens noch Fragen offen. visual studios.

                    OK, und da geht so etwas?

                    Viele Grüße
                    Robert

  2. Hallo hmm,

    was Du da vorhast, ist ein Unfall der darauf wartet zu passieren. Grund: Statische Elemente einer Klasse werden nicht von ihrem Destruktor beeinflusst. Warum auch - es ist sinnlos bis falsch, beim Zerstören einer Instanz auch die statischen Elemente zu beseitigen.

    D.h. du bist in deinem Destruktor auf die richtige Anordnung des Sourcecodes angewiesen, wenn Du auf irgendwas zugreifen willst, das nicht Teil dieser Klasseninstanz ist. Konstruktion und Destruktion erfolgen in entgegengesetzter Reihenfolge, das ist ein Stack, und weil du erst Bla erzeugst und dann a, wird zuerst a vernichtet und dann Bla.

    Das Konstrukt class Bla {...} Bla; macht dreierlei: Es definiert (1) die Klasse Bla, (2) allociert es eine Variable namens Bla vom Typ class Bla und (3) registriert es diese Variable zur Destruktion. Erst nach wird eine Variable a vom Typ std:string deklariert, ein Objekt vom Typ std:string erzeugt und mit einem Leerstring initialisiert.

    Trenne die Deklaration von class Bla und die Allocation der Variablen Bla, dann gehts:

    using std;
    
    class Bla
    {
      public:
        static string a;
        static char* b;
        ~Bla()
        {
          cout << "A ist " << a << endl;
          cout << "B ist " << b << endl;
        }
    };
    
    string Bla::a = "";
    char* Bla::b = "
    
    Bla Bla;
    
    irgendEinKlasse::FunktionDieAufgerufenWird()
    {
      Bla::a = "test";
      Bla::b = "test";
    }
    

    Ich würde Dir aber eigentlich empfehlen wollen, das so nicht zu tun. Du schaffst damit implizite Abhängigkeiten, die auf der lexikalischen Reihenfolge der Elemente im Code basieren. Es ist robuster, solche Abhängigkeiten explizit zu machen.

    D.h. statt eines static-Members a verwende ein non-static Member. Das ist dann eine normale Eigenschaft der Bla-Klasse, du kannst sie im Konstruktor von Bla initialisieren und dann ist eindeutig klar, dass es erst nach Ende des Destruktors von Bla zerstört wird.

    Alle Zugriffe auf deine Map lässt Du dann über die Bla Variable laufen (für die du sicherlich noch einen besseren Namen findest, und Du solltest vielleicht auch Abstand davon nehmen, Klassen und Variablen gleich zu benennen - mich hat das erstmal verwirrt.

    Dein std::... habe ich übrigens durch using std; ersetzt, und die printf durch cout. Musst nur an passender Stelle ein #include <iostream> hinzufügen (ohne .h dahinter!).

    Tja, und wenn Du das Ende des Programms finden musst - was für ein Typ von Programm ist das denn? Du hast doch normalerweise immer irgendeine main() Funktion. Wenn die endet, endet auch das Programm.

    Rolf

    --
    sumpsi - posui - clusi
  3. Moin @hmm,

    kleiner Nachtrag: Was ist denn das für eine printf-Funktion oder -Methode?

    static std::string a;
    static std::char* b;
    
    printf(a);
    printf(b);
    

    Das std::printf aus dem cstdio-Header nimmt als ersten Parameter einen const char*, aber keinen std::string.

    Viele Grüße
    Robert

    1. Hallo Robert,

      ich frag mich auch, welche C++ Umgebung hmm da verwendet. Mein Visual Studio erbricht sich über printf(Bla::a) genauso wie über die Deklaration std::char*

      Edit: Ok, std::char war ein typo... lesen vor posten hilft

      Rolf

      --
      sumpsi - posui - clusi
      1. danke, jetzt weiß ich schon mal wie ich das Programm zum laufen kriege!

        ich hab anstelle des printf eine komplette Ausgabefunktion für meine Map, hab das printf nur als Platzhalter reingeschrieben. Die ganze Klasse bla und die andere sind bei mir anders benamt, ich wollte zur besseren Übersicht nicht einfach alles rüberkopieren.

        Tja, und wenn Du das Ende des Programms finden musst - was für ein Typ von Programm ist das denn? Du hast doch normalerweise immer irgendeine main() Funktion. Wenn die endet, endet auch das Programm.

        ich sitze hier an einem sehr umfangreichen c++ Code der gezippt mehr als 2 GB verschlingt. Hier bau ich ein Analyseobjekt im Bereich der Komponententests ein damit es mitzählt welche "nicht sichtbaren" Tests wieviele Ereignisse auslösen. Mit diesen Infos wiederum kann man dann entscheiden ob bestimmten Module besser getestet werden müssen. Ich hab hier keine "main". Darum das Bla Objekt das in meinem code aktuell DetectShutdown heißt.

        1. Hallo @hmm,

          Die ganze Klasse bla und die andere sind bei mir anders benamt, ich wollte zur besseren Übersicht nicht einfach alles rüberkopieren.

          Nun ja, du siehst ja, wie „hilfreich“ unsere Tipps sind, wenn wir im Nebel herumstochern oder durch „Kerzen abgelenkt“ werden … ;)

          Tja, und wenn Du das Ende des Programms finden musst - was für ein Typ von Programm ist das denn? Du hast doch normalerweise immer irgendeine main() Funktion. Wenn die endet, endet auch das Programm.

          ich sitze hier an einem sehr umfangreichen c++ Code der gezippt mehr als 2 GB verschlingt.

          Kurze Zwischenfrage: Text wird normalerweise sehr effizient komprimiert. Dein Code müsste entpackt also deutlich größer sein. Ist das ein Betriebssystem oder was hat 20 ~ 100 GB Quellcode-Größe?

          Hier bau ich ein Analyseobjekt im Bereich der Komponententests ein damit es mitzählt welche "nicht sichtbaren" Tests wieviele Ereignisse auslösen. Mit diesen Infos wiederum kann man dann entscheiden ob bestimmten Module besser getestet werden müssen. Ich hab hier keine "main". Darum das Bla Objekt das in meinem code aktuell DetectShutdown heißt.

          So, das Logging funktioniert ja, nur das Auswerten noch nicht. Jetzt habe ich eben mal Folgendes ausprobiert und jetzt sagst du mir, was passiert:

          #include <cstdio>
          
          class Bla {
          public:
              Bla() {
                  puts("Bla::Bla");
              }
          
              ~Bla() {
                  puts("Bla::~Bla");
              }
          };
          
          Bla bla;
          
          int main() {
              puts("main");
              return 0;
          }
          

          Viele Grüße
          Robert