Objekt auf dem Heap oder Stack?


  • Administrator

    Grüsse zusammen,

    Ist es möglich, dass ein Objekt feststellen kann, ob es auf dem Heap liegt oder auf dem Stack? Also in etwa sowas:

    class CWoBinIch
    {
    public:
        void feststellen()
        {
            if(/* Rausfinden ob das Objekt auf dem Heap oder Stack ist */)
            { std::cout << "Auf dem Heap" << std::endl; }
            else
            { std::cout << "Auf dem Stack" << std::endl; }
        }
    };
    
    int main()
    {
        CWoBinIch WoBinIchStack;
        WoBinIchStack.feststellen();
    
        CWoBinIch* pWoBinIchHeap = new CWoBinIch();
        pWoBinIchHeap->feststellen();
    
        delete pWoBinIchHeap;
    
        return 0;
    }
    
    // Ausgabe:
    Auf dem Stack
    Auf dem Heap
    

    Worum es mir dabei geht? Ich möchte eine Klasse schreiben, welche ich auf dem Stack ablegen kann, allerdings auch auf dem Heap. Wenn sie auf dem Heap ist, soll der Speicher selber verwaltet und wenn nicht mehr verwendet, gelöscht werden.
    Ein "delete this" würde aber wohl fatale Folgen haben, wenn das Objekt auf dem Stack ist, also muss ich irgendwie unterscheiden können.

    Grüssli



  • Das was du da machen willst, ist meiner meinung nach ziemlich schlechter Stil.

    Lass dein Objekt doch durch einen Smart-Pointer wie boost::shared_ptr o.ä. Verwalten.



  • Du weißt doch, ob du es auf dem Heap oder Stack erstellt hast. Warum willst du das feststellen?


  • Administrator

    wasserzeitconverter schrieb:

    Du weißt doch, ob du es auf dem Heap oder Stack erstellt hast. Warum willst du das feststellen?

    Es wird eine Bibliothek, also weiss ich es nicht. Ich möchte die Benutzung dem Programmierer vereinfachen.

    @The-Kenny,
    Die sind mir bekannt, aber dann muss das Objekt immer auf dem Heap erstellt werden und das ist ein wenig blöd.

    Das Hauptproblem, welches ich zu lösen versuche ist, dass ich ein optionales Attribute in einem Objekt über den Stack lösen möchte.

    class Attribute
    {
    private:
        unsigned char uc1;
        unsigned char uc2;
        unsigned short us1;
    };
    
    class SomeClass
    {
    private:
        Attribute* m_pAttribute;
    
    public:
        set_attribute(Attribute* pAttribute) { m_pAttribute = pAttribute; };
        Attribute* get_attribute() { return m_pAttribute; };
    };
    

    Bei diesem kurzen Beispiel, kann ich ja einfach einen NULL-Pointer setzen, um das Attribute "auszuschalten". Das geht aber eben nur über Zeiger.

    Den ersten Zeiger (bzw. Objekt) erstellt entweder die Bibliothek, welcher dann ein Heap-Speicher werden muss, da es nicht nur ein temporäres Objekt wäre, oder der Zeiger (bzw. Objekt) wird vom User erstellt, wo es gut wäre, wenn er zwischen dem Stack oder Heap wählen könnte.

    Der Stack wäre praktisch, weil man diese Klassen unteranderem dafür benötigt, ein File zu speichern. Und dieser Speichervorgang ist etwas zeitkritisch. So könnte man die Objekte in einer Funktion, einfach nur auf dem Stack erzeugen.

    Das Problem am Ende ist dann, woher weiss die Bibliothek oder der User, ob dieser Zeiger in SomeClass auf den Stack oder Heap verweist und somit gelöscht oder nicht gelöscht werden soll?

    Aber womöglich bin ich etwas zu pingelig und sollte es einfach über eine interne Implementation lösen, so das jedes Objekt für sich selber Heap-Speicher allokiert und der User alle Objekte auf dem Stack ablegen kann, ohne etwas vom Speichermanagement mitzubekommen.
    Aber irgendwie will es mir nicht gefallen, da die Klassen zu klein sind und es eben mehr Zeit benötigt mit dem Heap zu arbeiten.

    Naja, nochmals ein paar Stunden die Sache überlegen und vielleicht bringt auch noch einer von euch eine Idee 🙂
    Oder dann muss ich zum Schluss kommen, dass User-Freundlichkeit und Geschwindigkeit nicht vereinbar sind.

    Grüssli



  • Ich würde auch sagen, dass du ja weisst, wo das ist, da du es erstellt hast. Sonst müsstest du hald einen interne Liste verwalten, die dir die Zeiger, die im free Store abspeichert. Ich denke, dass das mit boost einfacher wäre.



  • Du kannst sowas überprüfen, aber wie sinnvoll das ist, keine Ahnung...
    Hier mal ein Beispiel-Code, wie es geht. Ist nicht vollständig und muss natürlich nicht immer funktionieren (Stichwort: Multi-Threading). Auch musst du dich noch um Kopierkonstruktor usw. kümmern, falls du sowas überhaupt machen willst.

    #include <iostream>
    #include <cstdlib>
    
    class WhereAmI
    {
    public:
    	WhereAmI()
    	{
    		m_newWasUsed = newWasUsed;
    		newWasUsed = false;
    	}
    
    	void* operator new(size_t size)
    	{
    		newWasUsed = true;
    		return std::malloc(size);
    	}
    
    	bool wasDynamicallyCreated()
    	{
    		return m_newWasUsed;
    	}
    
    private:
    	bool m_newWasUsed;
    
    	static bool newWasUsed;
    };
    
    bool WhereAmI::newWasUsed = false;
    
    int main()
    {
    	WhereAmI a;
    	WhereAmI* b = new WhereAmI;
    
    	std::cout << "a is on the " 
    			  << (a.wasDynamicallyCreated() ? "Heap." : "Stack.")
    			  << std::endl;
    
    	std::cout << "b is on the " 
    			  << (b->wasDynamicallyCreated() ? "Heap." : "Stack.")
    			  << std::endl;
    
    	std::cin.get();
    	return 0;
    }
    

  • Administrator

    drakon schrieb:

    Ich würde auch sagen, dass du ja weisst, wo das ist, da du es erstellt hast.

    Ich habe schon mal gesagt, dass es eine Bibliothek wird. Das heisst, auch ein anderer kann diese Klassen benutzen und dem soll es so angenehm wie möglich gemacht werden.

    @Don06,
    Ok, das wäre eine Idee. Aber so richtig gefällt es mir schon nicht. Ist irgendwie mehr ein Hack, als eine saubere Lösung, oder? ^^'

    Naja, ich muss nun weg, ich überlege mir mein Konzept nochmals unterwegs, vielleicht finde ich auch einfach einen heftigen Designfehler, welcher alles auflöst.

    Danke euch.

    Grüssli



  • @Don06,
    Ok, das wäre eine Idee. Aber so richtig gefällt es mir schon nicht. Ist irgendwie mehr ein Hack, als eine saubere Lösung, oder? ^^'

    Naja, ich denke, dass ist mehr oder weniger den intelligenteste Ansatzt, den man da machen kann. Irgendwie musst du ja new/delete kapseln..



  • Der Grund, dass es eine Bibliothek werden soll, ist quatsch.
    Wenn der User das Objekt erzeugt, gibt er es wieder frei. Wenn die Bibliothek das Objekt erzeugt gibt die es wieder frei. Und jeder von beiden weiß, wo das Objekt erzeugt wurde.

    Außerdem: Wenn der User das Objekt, das die Bibliothek erzeugt, wieder freigeben soll, dann müsstest du eine Pointer auf nen Pointer übergeben. Aber das lass mal besser sein.


  • Administrator

    wasserzeitconverter schrieb:

    Wenn der User das Objekt erzeugt, gibt er es wieder frei.

    Sehr benutzerfreundlich.

    wasserzeitconverter schrieb:

    Und jeder von beiden weiß, wo das Objekt erzeugt wurde.

    Wo es erzeugt wurde ja, aber nicht wo es ist ^^
    Wenn du ein Objekt aus der Bibliothek hast, welches von der Bibliothek erzeugt wurde und du dann ein von dir selbst auf dem Heap erstelltem Objekt, diesem Objekt aus der Klasse übergibst und den Zeiger selber nicht weiter benötigst, wer gibt dann diesen Speicher frei? Wenn es die Bibliothek ist, dann müsstest du fest definieren, dass alle Objekte, welche der Bibliothek übergeben werden, auf dem Heap vorhanden sein müssen. Was wiederrum eine Einschränkung ist, welche ich ja nicht möchte.

    Also wenn du mich fragst, siehst du das ganze ein wenig zu einfach.

    wasserzeitconverter schrieb:

    Außerdem: Wenn der User das Objekt, das die Bibliothek erzeugt, wieder freigeben soll, dann müsstest du eine Pointer auf nen Pointer übergeben.

    Was du darunter verstehst, verstehe ich nicht. Ich sehe in gar keinem Fall, dass ich hier einen Pointer auf einen Pointer bräuchte.

    Aber wie auch immer, ich hatte ja auch schon selber gedacht, ob es nicht ein Designfehler hat und jemand hat es glaub ich auch hier angesprochen und tatsächlich ist es glaub ich auch der Fall. Zumindest ist mir auf dem Nachhause Weg noch eine Idee gekommen.

    Ich entkapsle das Zeug noch ein wenig mehr und mache etwas ähnliches, wie ich es aus den XML-Parsern/Schreibern kenne, nämlich DOM und SAX.
    Ich hätte so oder so noch Klassen schreiben müssen, welche die Objekte in eine Datei schreibt, bzw. die Objekte daraus liest. Ich baue nun diese ein wenig aus und mache sie zu Factories.
    Die Datenklassen baue ich aus und gebe ihnen einen "Referenzzähler". Jedes Objekt, welche das andere braucht, erhöht halt den Zähler, bzw. verkleinert ihn, sobald es nicht mehr benötigt wird. Bei 0 wird es gelöscht.
    Mit einer Factory kann man dann, wie bei SAX, alles nacheinander lesen, bzw. schreiben. Dies geht mit den gleichen Objekten, einfach auf dem Stack. Die Factory erhebt keinen Anspruch und auch sonst niemand, somit wird auch nie "delete this" aufgerufen.
    Der Factory kann man aber auch ein DOM Konstrukt übergeben, bzw. erhalten, welches sich auf dem Heap befindet. Da die Objekte in einander übergreifen, werden dann auch die Referenzen entsprechend verändert und die Objekte werden automatisch vom Heap gelöscht.

    So habe ich zwei Levels, das Obere benutzt das Untere und ist angenehmer, braucht dafür aber mehr Zeit. Das Untere ist sehr schlicht gehalten, ist dafür auch für zeitkritische Aufgaben verwendbar.

    Ich glaube so fahr ich ganz gut.

    Grüssli



  • Dravere schrieb:

    wasserzeitconverter schrieb:

    Wenn der User das Objekt erzeugt, gibt er es wieder frei.

    Sehr benutzerfreundlich....

    Japp !! Und guter Stil !!
    Man sollte sowieso nicht fremder Leute Speicher freigeben bzw. von ihnn freigeben lassen.

    Übigens: Es gibt einen ziemlich langen und gute Artikel über genau diese Frage gelesen (Scott Meyers, Herb Sutter, ... ?) und der kam nach einer sehr gründlichen Untersuchung zum Schluss: Man kann NICHT zuverlässig herausfinden, ob ein Objekt deleted werden darf oder nicht.

    Ich würde das Thema begraben und mich Wichtigerem zuwenden.

    Gruß,

    Simon2.



  • Es war Scott Meyers in More Effective C++ Mit dem Artikel über das Thema



  • Wo es erzeugt wurde ja, aber nicht wo es ist ^^
    Wenn du ein Objekt aus der Bibliothek hast, welches von der Bibliothek erzeugt wurde und du dann ein von dir selbst auf dem Heap erstelltem Objekt, diesem Objekt aus der Klasse übergibst und den Zeiger selber nicht weiter benötigst, wer gibt dann diesen Speicher frei?

    simpelste Lösung: verpass dem Zeiger referenzzählung. boost::shared_ptr wär da ein Ansatz(referenzzählung hast du ja später selbst erwähnt). In dem Fall würde die bibliothek den Speicher automatisch wieder freigeben, sobald der letzte shared_ptr auf das Objekt gekillt wurde.

    Wenn es die Bibliothek ist, dann müsstest du fest definieren, dass alle Objekte, welche der Bibliothek übergeben werden, auf dem Heap vorhanden sein müssen. Was wiederrum eine Einschränkung ist, welche ich ja nicht möchte.

    Nein. der bibliothek ist egal, was du ihr übergibst. die Bibliothek verwaltet nur die Objekte, die sie selber erstellt hat. Sie kann ja auch nicht wissen, wie du die Objekte erstellt hast. Beispiel shared memory: wenn das Objekt im shared memory liegt, kann es sein, dass dieser Speicherbereich wieder freigegeben werden muss, oder einem anderen Objekt mitgeteilt werden sollte, dass da wieder was frei ist. Nur, wie soll deine Bibliothek das machen? new Glaskugel?

    Also wenn du mich fragst, siehst du das ganze ein wenig zu einfach.

    Es ist auch verdammt einfach ;). Jeder zerstört die Objekte, die er erstellt und verwaltet hat. Anders wird es sofort komisch, std::vector verlangt von dir ja auch nicht, dass du die Objekte von hand zerstörst.



  • Dravere schrieb:

    Die Datenklassen baue ich aus und gebe ihnen einen "Referenzzähler". Jedes Objekt, welche das andere braucht, erhöht halt den Zähler, bzw. verkleinert ihn, sobald es nicht mehr benötigt wird. Bei 0 wird es gelöscht.

    Das ist nicht wirklich benutzerfreundlich, weil man dann oft vergisst eines von beiden zu machen. Dann doch lieber boost::shard_ptr oder was ähnliches. Bist du dir sicher, dass du wirklich sowas brauchst? Du redest immer von zeitkritisch. Hast du schon mal gemessen, ob der Teil wirklich der zeitkritische ist, oder denkst du das nur? So wirklich hab ich noch nicht verstanden, was du da schneller machen willst. Vielleicht macht der Compiler schon das was du da verbessern willst. Google: Named Return Value Optimization


  • Administrator

    Also zuerst mal wieder ein Beispiel:

    class SomeClassOfTheLibrary
    {
    };
    
    class ASecondClassOfTheLibrary
    {
    private:
        SomeClassOfTheLibrary* m_pClass;
    
    public:
        set_class_pointer(SomeClassOfTheLibrary* pClass) { m_pClass = pClass; };
        SomeClassOfTheLibrary* get_class_pointer() { return m_pClass; };
    };
    
    // Irgendwo ganz am Anfang eines Programmes mit dieser Bibliothek
    {
        // ....
        // m_ASecondClassOfTheLibrary ist hier ein Objekt von eben dieser Klasse
        m_ASecondClassOfTheLibrary.set_class_pointer(new SomeClassOfTheLibrary);
        // ....
    }
    
    // Und irgendwo am Ende des Programmes, muss der Programmierer jetzt noch dran denken
    {
        // ...
        delete m_ASecondClassOfTheLibrary.get_class_pointer();
        // ...
    }
    

    Ich würde eher sagen, dass ist nicht benutzerfreundlich, sondern ein gesichertes Memory Leak.
    Der Vergleich mit std::vector hinkt auch etwas, weil man dem std:vector alles übergeben kann. Das kann man bei meinen Klassen nicht, da sind es nur ausgesuchte Klassen, welche alle auch in der Bibliothek vorkommen. Nur erstellt man halt die Objekte davon selber.

    Zum Zeitkritischen:
    Naja, sagen wir es so, die Heap Version braucht nach gewissen Testmessungen, ca. 2 Sekunden, während der Stack es in den Millisekunden schafft.
    Es kommt halt eben drauf an, was man macht.

    Zu Boost:
    Möchte ich nicht verwenden, da ich hier auf reinem C++ verbleiben möchte. TR1 ist immer noch nicht Standard, somit fällt der shared_ptr weg.
    Aber ich kann ja, wie schon gesagt wurde und ich gesagt habe, selber was machen 😉

    Grüssli



  • Was soll der Sinn des ganzen sein? Du erzeugst ein Objekt ganz am Anfang eines Programmes setzt dieses in einer anderen Klasse und am Ende des Programmes gibst du es wieder frei. Warum wird das SomeClassOfTheLibrary Objekt nicht gleich im Konstruktor von ASecondClassOfTheLibrary erzeugt und im Destruktor wieder gelöscht? Und dann gibst du im get immer nur nen const Pointer zurück.

    Zum Zeitkritischen:
    Naja, sagen wir es so, die Heap Version braucht nach gewissen Testmessungen, ca. 2 Sekunden, während der Stack es in den Millisekunden schafft.
    Es kommt halt eben drauf an, was man macht.

    Was hast du denn da gemessen?
    10000 mal

    void foo() {
       Bla* bla = new Bla();
    }
    

    vs.

    void foo() {
       Bla bla;
    }
    

    Oder sowas?


  • Administrator

    @wasserzeitconverter,
    Du könntest ja zum Beispiel auch am Anfang, set_class_pointer(NULL) setzen. Es kann auch sein, dass du das Objekt nur kurz brauchst. Es kann aber eben auch sein, dass du es wie oben, zu Beginn setzen musst und am Ende wieder entfernen. Ich kann ja nicht definieren, wo der User den Pointer setzen wird. Bzw. er muss ihn ja nicht mal setzen!

    Und zur Zeitmessung, ne, ich habe den ganzen schon vorhanden Code genommen und im Release Modus kompiliert. Dazu den DevPartner Profiler genommen (wow, hätte nicht gedacht, dass ich den so schnell wieder brauchen werde ^^). Es gibt noch andere Stellen die Zeit brauchen, aber durch die Heap->Stack umstellung, kann ich in gewissen Situationen, ca. 2 Sekunden einsparen.

    Mit meiner neuen Idee für den Aufbau der Bibliothek, habe ich für alle Situationen, zumindest meiner Meinung nach, eine gute Lösung bereit.

    Grüssli



  • Vielleicht verrätst du einfach mal was die Bibliothek machen soll und wozu du dieses komische Pointer setzen brauchst. Sehr wahrscheinlich gibts ne viel besser Lösung für das was du vor hast. Das man nur da Speicher auf dem Heap reserviert wo es sein muss und sonst immer den Stack verwendet sollte klar sein. Deine Verwendung von Heap/Stack schaut mir irgendwie sehr komisch aus.



  • wasserzeitconverter schrieb:

    Vielleicht verrätst du einfach mal was die Bibliothek machen soll und wozu du dieses komische Pointer setzen brauchst. Sehr wahrscheinlich gibts ne viel besser Lösung für das was du vor hast. Das man nur da Speicher auf dem Heap reserviert wo es sein muss und sonst immer den Stack verwendet sollte klar sein. Deine Verwendung von Heap/Stack schaut mir irgendwie sehr komisch aus.

    ich *vermute* mal, er will eine Art Objektbaum basteln, und das soll auf 2 Arten Möglich sein:

    //1. Art wenn bekannt ist, wie der Baum aussehen soll
    Foo o1;
    Foo o2(&o1);
    Foo o3(&o2);
    
    //2. Art wenn es von irgendeiner Eingabe abhängt
    //alternativ auch mit irgendeiner Factory
    Foo* o1=new Foo();
    Foo* o2=new Foo(o1);
    Foo* o3=new Foo(o2);
    

    und Dravere sucht jetzt nen Weg, wie er das Speichermanagement bei der heap allocation geregelt bekommt. Da wir nicht wissen, inwiefern die lebensdauer determiniert ist, können wir ihm da aber nicht helfen.

    könnte sein, dass sowas reicht:

    Foo* o1=new Foo();
    Foo* o2=new Foo(o1);
    Foo* o3=new Foo(o2);
    
    //dosomething...
    ...
    //end DoSomething
    delete o3;
    delete o2;
    delete o1;
    

    für den Fall würde eine Factory reichen, die gleichzeitig besitzer der Objekte ist. wenn die Factory zerstört wird, nimmt sie ihre Objekte dann direkt mit.

    Vielleicht reichts auch nicht, dann braucht man eine referenzzählung. Die würde ich aber niemals in das Objekt selber auslagern, sondern in form eines smart pointers kapseln.

    @Dravere wegen deines tests: nutzt du einen Small Objekt allokator? Sonst ist dein benchmark nicht wirklich aussagekräftig, sondern eher auf dem niveau der üblichen java vs C++ Benchmarks.


  • Administrator

    Der Objektbaum, beschreibt es ziemlich gut. Grundsätzlich sind es einfach Metadaten, welche optionale Attribute haben, welche wiederrum Metadaten sein können (Metadaten über Metadaten? 🙂 ).

    Das mit der Lebensdauer ist ja genau mein Problem. Die Objekte, welche nur von der Bibliothek verwendet werden und ich die Kontrolle drüber habe, sind nicht das Problem. Da weiss ich genau, wie die Lebensdauer aussieht. Aber sobald jemand die Bibliothek benutzt, dann ist es möglich, dass er einzelne Metadaten selber setzen kann (logisch, nicht?). Und diese Lebensdauer kenne ich halt nicht und suche dafür trotzdem eine gute Lösung, welche so flexibel, angenehm und optimal für den Benutzer ist, wie nur möglich.

    Also wenn die Lebensdauer länger ist, wird er wohl den Heap dafür benutzen und wenn es kurzfristig ist den Stack.

    Wenn es längerfristig ist und er den Heap benutzt, er dann aber auch den Speicher selber wieder freigeben muss, dann bin ich mir sicher, führt das zuerst mal zu Memory Leaks. Und als nächtes zu einem Brummen, weil die Bibliothek nicht angenehm ist 😉

    Die temporäre Benutzung des Stacks, schliesst aber shared_ptr aus und auch die Möglichkeit den Pointer auf NULL zu setzen (Es soll ja optional sein), was bei shared_ptr nicht möglich ist.

    Deshalb bin ich ja eben jetzt soweit, dass ich zwei Layer anbiete, einen für die schnelle Stack Benutzung und den anderen für die längere, dynamische Benutzung.

    Das einzige Problem, welches somit bleibt, ist: "Wer gibt den vom User allokierten Speicher frei, bzw. wie?"

    Ich bin halt eigentlich der Meinung, wenn man Speicher anlegt und diesen zur Verwaltung der Bibliothek übergibt, dann soll die den auch wieder freigeben. Und wenn so ein Referenzzähler direkt an das Objekt gekoppelt ist, dann kann man einfach selber noch ein increment_reference(); aufrufen, bevor man das Objekt der Bibliothek übergibt und man ist selber für die Freigabe des Speichers verantwortlich.

    otze schrieb:

    @Dravere wegen deines tests: nutzt du einen Small Objekt allokator? Sonst ist dein benchmark nicht wirklich aussagekräftig, sondern eher auf dem niveau der üblichen java vs C++ Benchmarks.

    Da ich ehrlich gesagt nicht weiss, was ein Small Object Allocator ist, denke ich, hat mein Test mir falsche Werte geliefert 🙂

    Grüssli


Anmelden zum Antworten