Speicher wieder freigeben

#1

Hallo zusammen,

ich habe in meinem kleinen Programm häufiger Funktionen, die folgenderweise aussehen:

	int size;
	Cluster* cluster;
	
	ClusterContainer add(Cluster* c) {
		ClusterContainer result = {};
		result.size = this->size + 1;
		Cluster* newCluster = new Cluster[result.size];
		for (int i = 0; i < this->size; i++) {
			Cluster* current = this->cluster + i;
			Cluster* insertPlace = newCluster + i;
			memcpy(insertPlace, current, sizeof(Cluster));
		}
		memcpy(newCluster + (this->size), c, sizeof(Cluster));
		result.cluster = newCluster;
		return result;
	}
};```

Hier arbeite ich mit **memcpy**, um Speicherinhalte zu kopieren. Das Resultat ist ein neues Struct, mit dem ich dann im Programm weiterarbeite.
Das alte Struct benötige ich an der Stelle nicht mehr, muss ich den Speicher, den dieses Struct belegt hat, dann nicht wieder freigeben?

Das Programm funktioniert, es ist ja auch recht klein, aber ich denke, dass es so nicht wirklich sauber ist.
#2

Moin,

mal ganz lax gesagt: alles was Du mit “new” allokierst, solltest Du auch wieder freigeben!
C+±Programmierung/ Speicherverwaltung/ new und delete

Gruß Klaus

#3

Danke für den Link, habe jetzt dementsprechend umgeschrieben.

Eine weitere Frage hätte ich noch, und zwar hatte ich beispielsweise vorher so etwas stehen:
clusters = clusters.remove(toMerge->c1).remove(toMerge->c2).add(&merged)

Daraus ist jetzt folgendes geworden:

ClusterContainer remove2 = remove1.remove(toMerge->c2);
ClusterContainer added = remove2.add(&merged);
delete &remove1;
delete &remove2;
delete &clusters;
clusters = added;```
Was ich persönlich etwas unschön finde ^^

Gibt es die Möglichkeit, in der remove() und add() direkt das Struct zu löschen?
Ich vermute eigentlich nicht, da ich ja gerade darauf operiere (und mir 

delete this;

 auch um die Ohren fliegt, hehe).

Gibt es für sowas ein allgemeines Pattern, oder macht man das so, wie ich es jetzt habe?
#4

Vielleicht wäre “GarbageCollection für Arme” eine Lösung: ein globales Objekt, dem Du Adresse und Größe des frei zu gebenden Speichers übergibst, und dass dann regelmäßig in einem eigenen Thread die Liste abarbeitet. Da könnten sich dann solche Temp-Objekte selbst hin übergeben.

Problem dabei könnte sein, das C++ nicht wirklich OO ist und man zur Laufzeit das Objekt nicht nach seiner Klasse fragen kann, um den Pointer für die Übergabe an delete passend zu casten…

bye
TT

#5

Man muss sich natürlich genau überlegen, welche Lösung für welchen Anwendungsfall die geeignetste ist, aber … wenn man mit solchen Strukturen hantiert, wo “Objekte” hin- und her gereicht werden, dann bietet sich oft ein std::shared_ptr an. (Seit C++ 11 gehört das zum Sprachkern. Falls du eine Ältere C+±Version verwendest, kannst du stattdessen boost::shared_ptr verwenden. Das IST praktisch genau das, was später zum std::shared_ptr wurde, d.h. wenn du auf C++11 wechselst, ist die Portierung einfach: “Replace all ‘boost::shared_ptr’ with ‘std::shared_ptr’”)

Jedenfalls kann man diese shared_ptr praktisch genau so verwenden, wie Java-Objekte: Man reicht sie hin und her, und wenn es keine Referenzen mehr drauf gibt, wird der Speicher automatisch deallokiert. Im Idealfall hat man dann GAR nicht mehr mit “new” und “delete” zu kämpfen.

Im konkreten Beispiel könnte dann… ja, die komplette Klasse “ClusterContainer” wegfallen :slight_smile:

Stattdessen gäbe es nur noch die Klasse/Struct “Cluster” (wie auch immer die aussieht), und der Container wäre eben ein

typedef std::shared_ptr<Cluster> ClusterPtr;
typedef std::vector<ClusterPtr> ClusterContainer;

Und in so einen ClusterContainer könnte man dann ganz einfach Cluster einfügen:

ClusterContainer container;
container.push_back(std::make_shared<Cluuster>());

std::cout << "Size " << container.size() << std::endl;
std::cout << "First cluster: " << container[0]->someStringThatDescribesTheCluster() << std::endl;

(Nebenbei: Die STL-Container-Klassen zu verwenden hat vieeeele Vorteile. Man kann z.B. die ganze Maschinerie von darauf loslassen)

(EDIT: Und noch nebenbeier: Manuelle array-Verwaltung mit Something* array = new Something[10] usw. ist, soweit ich das mitbekommen habe, sowieso “out” ;-))

#6

Runtime Type Information … ohne RTTI wären eine dynamic_cast<>() nicht möglich

deswegen macht man Destruktoren immer virtuell um sich das Cast-Geraffel zu sparen

#7

Die sollte man (wenn möglich) eh komplett vermeiden.

#8

Danke für die zahlreichen Antworten, ich hatte jetzt am Wochenende leider keine Zeit für das Projektchen, aber ich werde mal zusehen, dass ich einige eurer Anregungen umsetze :wink:
@Marco13 : Der Grund, weshalb mein Projekt neuere Spracheigenschaften vermissen lässt, ist vermutlich, dass ich durch das Projekt “Handmade Hero” von Casey Muratori beschlossen habe, ein paar Sachen in C++ zu machen. Casey schreibt eher “C mit C++ gewürzt”, daher kenne ich viele Konstrukte gar nicht :wink:

#9

Problem dabei könnte sein, das C++ nicht wirklich OO ist und man zur Laufzeit das Objekt nicht nach seiner Klasse fragen kann,

Hust aehm …
Also C++ ist schon OO ^^
Nur weil es kein Reflektion oder so anderen Schmutz kann … :slight_smile:
Also Laufzeit Typ informationen haben mit OO eigentlich gar nix zu tun .
Und C++ kann dies auch, nur eben ned so komfortabel wie java ^^
Aber wegen performance hat man rtti (runtime type information) eh abgeschalten.
Und dynamic casts gelten als Design Problem
@Shadoka
Speicher allokierung Design Rules für Fortgeschrittene:

new und delete immer im entsprechenden Context setzen.
Also new im Konstruktr, delete im Destruktor ist ok
new am anfang eines Blockes und delete am ende ist ok
New in einer create methode, delete in der dazu passenden release methode ist ok

Alles andere ist eh vom Design her zweifelhaft.
Funktionen die allokierten Speicher zurueckgeben und dem Anwender das Freigeben aufdrücken sind so ziemlich die miesesten Ideen ^^

Und weiter, rohes new und delete meiden, nur verwenden wenn es gar ned anders geht …
besser: container dafuer verwenden (die verschieben dir zeugs eh auch aufn heap)
nen roher speicherbereich wo man per zeigerarithmetik draufrumrutschen kann, bekommst du auch ueber nen vector oder entsprechende klassen anderer bibos.
Wenn doch new, dann bitte in verbindung mit smartpointern …

Bei Schnittstellen über dll/so grenzen kommt noch mal was mehr dazu … weil man da keine templates ueber schnittstellen schicken sollte …
Da orientiert man sich an C … und arbeitet meist mit buffern die man komplett in die andere Übersetzungseinheit per parameter injiziert …

Ciao …

#10

[quote=HBerger]Also C++ ist schon OO ^^[/quote]Aber ehr im Sinne von “oh oh” als “objektorientiert”.
JA, man kann mit Klassen und Objekten arbeiten.

Aber letztlich ist eben alles wieder nur “flat memory” mit dem der Programmier machen kann, was er will.
Und dann die Veröffentlichung privater Methoden und variablen im Header, die so merkwürdige Konstrukte wie PIMPL erfordert.

Aber wenn man das mag…

bye
TT

#11

Naja es geht weniger ums mögen …
Wenn man als C++ mal in die Java Welt reinschnuppern durfte, dann vermisst man schon einiges wenn man zurück muss ^^
z.b.
Aspect Oriented Programming, Depencency Injektion, gescheites configurationsmanagment …
Was man damit alles in C++ machen könnte ^^
Ok einige Ansätze gibts schon, aber wenn man dann wieder an die Laufzeitperformance denkt, läßt man es dann doch wieder ^^

Und dann die Veröffentlichung privater Methoden und variablen im Header, die so merkwürdige Konstrukte wie PIMPL erfordert.

Naja man veröffentlich ja nur die Signatur … das gibt ned so viel Preis … also allein nur das wer die signatur der privaten methoden sieht, dafuer wuerd ich ned pimpln.
Gibt Schlimmere Dinge, weswegen Pimpln ab und an ne gute idee ist ^^

Wenn man auf Library Ebene programmiert, beglückt man seine Code Anhänger doch sowieso lieber mit Interfaces … also pure abstract classes wie sie in c++ heissen, was ja auch ne impliziete Form des Pimpln ist.
Und das machen javaler doch hoffentlich auch nicht so selten ??? Also nicht des Pimpeln wegens ^^

Kann man java Bytecode eigentlich gegen reflektion absicheren ??? also mit reflektion krieg ich doch theorethisch in Java auch die Signaturen der privaten Implementationsklassen raus oder ?

Ciao ^^

#12

[quote=HBerger]Naja man veröffentlich ja nur die Signatur … das gibt ned so viel Preis …[/quote]Es geht um die compile-Abhängigkeiten: Wenn ich kein PImpl habe, müssen alle benutzer meiner Klasse neu Kompilieren, weil ich eine private Methode hinzugefügt habe. Und dass, wo C++ sowieso schon nicht die Rakete beim Kompilieren ist…

bye
TT

#13

Es geht um die compile-Abhängigkeiten:

genau !

Und dann die Veröffentlichung privater Methoden und variablen im Header

Klang mehr wie Angst davor das wer sieht was verwendet wurde als wie Respect vorm AbhaengigkeitsManagment … ^^

Und dass, wo C++ sowieso schon nicht die Rakete beim Kompilieren ist…

Ja, aber mit etwas Grips kann man sich da schon arrangieren … bzw. Milderung erarbeiten.
Aber richtig ist, das viele das AbhaengigkeitsManagment voll vernachlässigen. Dann gibts auch ordentlich Compile-Zeiten :slight_smile:
Aber in nem Produktgetriebenen Projekt ist das noch das kleinere Problem … schlimmer ist das updates ned rauskommen solange Lib XYZ … ned angepasst wurden.
Oder das ein Umstieg auf ne neue LIB/Compiler/Framework gar ned möglich ist, weil man sonst 12034729029045 Projekte nachziehen muesste …
Ich leide da aktuell auch grad wieder …

Ciao …

#14

[quote=HBerger]Ja, aber mit etwas Grips kann man sich da schon arrangieren[/quote]Sicher, aber in einer “richtigen” OO-Sprache muss man das nicht… ;o)

bye
TT

#15

Hui, was passiert denn mit meinem alten Thread hier… :eek:

Ich bin dann sowieso auf Go umgeschwenkt (https://github.com/Shadoka/clustering_go), manuelle Speicherverwaltung scheint mir wohl nicht so zu liegen, vielleicht wage ich mich da später nochmal ran :wink:

Aber danke nochmal für eure zahlreichen Hinweise!

#16

[ot]

Man kann erreichen, dass (Compile)abhängigkeiten minimiert werden, und damit der Aufwand für das Neu-Bauen geringer wird. Allerdings erfordert das (paradoxerweise) viel Aufwand (!) und Disziplin. Ein einzelnes unbedachtes include kann die ““schönste”” Projektstruktur über den Haufen werfen. Tipp: In Visual Studio unter den Compile-Einstellungen (C++ -> Advanced) mal “Show Includes” einschalten, und schauen, was da so im Hintergrund abläuft. Als Hauptsursache dafür sehe ich das schon mehrfach gescholtene Konzept der Headers/Includes an sich, das einfach eine Riesige Text-Ersetzungs-Engine (und nebenbei eine zweite Programmierprache) ist. Wenn man in Java eine “Person” mit einem “Haustier” modellieren will, schreibt man class Person { Pet pet; } class Pet {} und ist dann soweit schon praktisch fertig (ggf. noch “Generate getters” in der IDE anklicken ;)) Bei C++ darf man sich erstmal überlegen, ob ein Haustier denn ohne eine Person existieren kann (Stichwort Ownership - das ist dann eigentlich schon wieder “On Topic”), und selbst wenn man sich das Leben in der Hinsicht leicht machen will, und pragmatisch ALLES über std::shared_ptr modelliert, darf man für eine einzelne Klasse (!) mal locker 4-6 Dateien anlegen: Header, Implementierung, Inlines, Forwards, und wenn man Pimpln will, noch den ImplHeader und die ImplImpl. Selbst wenn man alles “richtig” macht: Eine Library, die man irgendwo compiliert hat, kann schlicht und ergreifend nicht woanders verwendet werden. Das ist ein Armutszeugnis für alle, die dafür verantwortlich sind (wer auch immer das ist: Die Konsortien? Die Community? Die Compilerbauer?..)
Sicher: Die richtige Sprache für den jeweiligen Anwendungszweck. Aber ich behaupte: In eine Großteil der Fälle, wo C++ verwendet wird, ist die vorgeschobene Begründung “Wegen der Performance” (und wir wissen, dass man mit dieser Aussage knietief im Bockmist steht), und die tatsächliche Begründung eher: “Die verantwortlichen Entwickler haben keine Ahnung (von Java)”.

[/ot]

#17

[OT][quote=Marco13]die tatsächliche Begründung eher: “Die verantwortlichen Entwickler haben keine Ahnung (von Java)”.[/quote]Oder plain C.[/OT]
bye
TT