Verschiedene Objekte mit wiederkehrender Arbeit initialisieren
-
daddy_felix schrieb:
Warum hantierst du mit rohen Arrays? Die Dinger soltest du mal ganz schnell durch std::vector ersetzen.
Wie meinen? Wenn ich mit
vector
arbeite lande ich doch wieder direkt beim Ausgangsproblem.daddy_felix schrieb:
Ich hoffe, das Speicherloch hier ist in deinem echten Code nciht vorhadnen:
a_foo[0] = new foo(n);
Guter Punkt! Habe ich gefixt.
Gruß,
-- Klaus.
-
Klaus82 schrieb:
daddy_felix schrieb:
Warum hantierst du mit rohen Arrays? Die Dinger soltest du mal ganz schnell durch std::vector ersetzen.
Wie meinen? Wenn ich mit
vector
arbeite lande ich doch wieder direkt beim Ausgangsproblem.Du hast nicht wirklich begriffen, wo das Problem bei deinem Beispiel liegt?
double* const p_array;
Mach daraus einen std::vector. Dann lösen sich all deine Probleme in Luft auf. Du hast keine Konflikte mit der Rule of Three, das struct ist kopierbar und somit kannst du das struct in Containern verwalten und du musst die Größe des Arrays nicht immer separat mitschleppen.
-
Ich weiß nicht genau was ich nicht begriffen haben soll. Auf jeden Fall scheine ich mit dem folgenden Minimalbeispiel (siehe unten) genau den Kern meines Problems zu treffen:
Ich initialisiere zunächst die Struktur
foo
, welche im Konstruktor zur Laufzeit einen array erzeugt.
Diesen Array möchte ich bei der Initialisierung vonbar
an eben jene Struktur übergeben.Ich dachte, wenn ich kein
&
verwende, dann machte ich call-by-value, d.h. ich kopiere den Array ausfoo
nachbar
, d.h.bar
besitzt nun seine eigene Kopie. Also zeigt auf einen neu angeforderten Speicherbereich.Allerdings ist es wohl so, dass ich tatsächlich nur die Addresse übergebe. D.h. der Array in
bar
zeigt jetzt auch auf den Speicherbereich des Arrays infoo
.Schließlich bekomme ich am Ende des Programms einen Speicherzugriffsfehler. Wenn (k.A. wer offiziell zuerst kommt)
foo
vernichtet wird und seinen Destruktor aufruft, dann gibt es den Speicher wieder frei, wohin der Array zeigt.
Wenn dannbar
seinen Destruktor aufruft, dann ist der Speicher allerdings schon freigegeben und somit gibt mir Valgrind einen Fehler aus:==3105== Invalid free() / delete / delete[] ==3105== at 0x4023503: operator delete[](void*) (vg_replace_malloc.c:409) ==3105== by 0x804888D: foo::~foo() (mb.cpp:21) ==3105== by 0x8048963: main (mb.cpp:59) ==3105== Address 0x42b9028 is 0 bytes inside a block of size 80 free'd ==3105== at 0x4023503: operator delete[](void*) (vg_replace_malloc.c:409) ==3105== by 0x80488E5: bar::~bar() (mb.cpp:45) ==3105== by 0x8048957: main (mb.cpp:59)
Um das Problem zu umgehen habe ich (sicherlich umständlich und nicht ganz korrekt) eine Funktion in
foo
eingerichtet, welche tatsächlich die Addresse zurückgibt und anschließend den Pointer auf die Null setzt. Immerhin soll der Speicherbereich fortanbar
gehören undfoo
soll bereit sein für neu zu erzeugende Arrays.double* const get_adress(); double* const foo::get_adress() { double* const tmp = p_array; p_array = NULL; return tmp; }
Wodurch ich
bar
mittelsstruct bar b(f.get_adress());
aufrufen kann und alles scheint in Butter!
Vielen Dank fürs durchalten und lesen.
Viele Grüße,
-- Klaus.// mb.cpp #include <iostream> struct foo { foo(unsigned int const); ~foo(); unsigned int const n; double* p_array; }; foo::foo(unsigned int const x): n(x), p_array(new double [n]) { for(unsigned int i = 0; i < n; ++i) p_array[i] = i; } foo::~foo() { delete [] p_array; } struct bar { bar(double* const); ~bar(); double* const p_array; }; bar::bar(double* const p): p_array(p) {} bar::~bar() { delete [] p_array; } int main() { unsigned const n = 10; struct foo f(n); struct bar b(f.p_array); std::cout << "Time to clean up!" << std::endl; return 0; }
-
-
Was mein Vorposter bereits verlinkt hat, will ich dir nun kurz erklären.
Die Regel der Drei besagt:
Wenn man einen der folgenden Drei Funktionen definiert: Destruktor, Zuweisungsoperator oder Kopierkonstruktor - sollte man alle Definieren.
Denn höchstwahrscheinlich wird in diesen Funktionen etwas getan (bspw. Resourcen-Verwaltung), was in den anderen auch getan werden sollte. Da die vom Compiler generierten Default-Versionen dieser Funktionen eben einfach etwas falsches tun könnten, wie nicht den Pointee sondern den Pointer zu kopieren.Dein Beispiel hat die Macke, dass es wirklich unschön gelöst ist.
Was du wirklich willst, ist nicht eine komische Funktion die etwas völlig unintuitives tut (ein Getter ist i.d.R. immerconst
-qualifiziert und verändert das Objekt nicht), sondern die Regel der großen Drei beachten:struct foo { foo(unsigned int const); foo( foo const& ); ~foo(); foo& operator=(foo f); foo& swap( foo& ); /// Optional, verwende ich hier für das Copy&Swap-Idiom unsigned int const n; double* p_array; };
Und die Definition sieht folgendermaßen aus:
foo::foo( foo const& f ): n(f.n), p_array( new double[n] ) { std::copy( f.p_array, f.p_array + n, p_array ); // Array rüberkopieren } foo& foo::operator=(foo f) /// Diese Art, den Zuweisungsoperator zu definieren nennt man das Copy&Swap-Idiom. Google es mal. { return swap(f); } foo& foo::swap( foo& f) { /// Hier werden nur die unterliegenden Skalare kopiert, nicht das Array o.ä. std::swap(f.n, n); std::swap(f.p_array, p_array); return *this; }
Was du also tust ist, die korrekte Verwaltung der Ressourcen - kopieren, zerstören, usw. - gewissermaßen zu erzwingen, in dem du die entsprechenden Funktionen definierst. Dadurch kann gar nix mehr schiefgehen, rein theoretisch nicht.
Also schön RAII beachten.
Da du
p_array
einen ekligen Präfix (p_
) dranhängst, verweise ich dich auf Hungarian Notation.Code ist übrigens net getestet.
-
Sone schrieb:
Code ist übrigens net getestet.
eyyy gooooil, oldääär!
sogar netzwerkgetesteten code gibbet hier!
-
Sone schrieb:
struct foo { foo(unsigned int const); foo( foo const& ); ~foo(); foo& operator=(foo f); foo& swap( foo& ); /// Optional, verwende ich hier für das Copy&Swap-Idiom unsigned int const n; double* p_array; };
Und die Definition sieht folgendermaßen aus:
foo::foo( foo const& f ): n(f.n), p_array( new double[n] ) { std::copy( f.p_array, f.p_array + n, p_array ); // Array rüberkopieren } foo& foo::operator=(foo f) /// Diese Art, den Zuweisungsoperator zu definieren nennt man das Copy&Swap-Idiom. Google es mal. { return swap(f); } foo& foo::swap( foo& f) { /// Hier werden nur die unterliegenden Skalare kopiert, nicht das Array o.ä. std::swap(f.n, n); std::swap(f.p_array, p_array); return *this; }
foo::~foo() { delete[] p_array; }
-
...
-
Swordfish schrieb:
template< typename T > class foo_t { std::size_t /* warum zum teufel const */ size; T * data;
Warum nicht const?
Swordfish schrieb:
public: foo_t( std::size_t const size )
Ich frag mich eher, was das const hier soll :p
-
...
-
Swordfish schrieb:
out schrieb:
Swordfish schrieb:
template< typename T > class foo_t { std::size_t /* warum zum teufel const */ size; T * data;
Warum nicht const?
Weils't dann gleich ein Array nehmen kannst. :p
Verstehe ich gerade nicht. Wie meinst?
(mal schauen obs morgen früh klarer wird
)
-
...
-
Klaus82 schrieb:
Auf jeden Fall scheine ich mit dem folgenden Minimalbeispiel (siehe unten) genau den Kern meines Problems zu treffen:
Warum besitzt das struct ein rohes Array?
double* p_array; // warum kein std::vector???????
Wenn du dieses Array durch einen std::vector ersetzt, erleichterst du dir das Leben ungemein!
-
...
-
daddy_felix schrieb:
Klaus82 schrieb:
Auf jeden Fall scheine ich mit dem folgenden Minimalbeispiel (siehe unten) genau den Kern meines Problems zu treffen:
Warum besitzt das struct ein rohes Array?
double* p_array; // warum kein std::vector???????
Wenn du dieses Array durch einen std::vector ersetzt, erleichterst du dir das Leben ungemein!
Eine schlichte Antwort ist einfach, dass ich die ganzen Methoden eines STL Containers nicht benötige.
Wie gesagt, zum Einen möchte ich in einer Struktur Arrays bereithalten, welche die GSL dann verwendet, um interpolierte Werte zu berechnen.
Und zum anderen möchte ich einen Array von solchen Strukturen, genauer einen Array mit Pointern auf solche Strukturen, anlegen, um die verschiedenen Strukturen zu verwalten und mittels Index darauf zugreifen zu können.
Damit ist die Verwendung eines STL Containers für mich 'mit Kanonen auf Spatzen schießen' in diesem Zusammenhang.
Gruß,
-- Klaus.
-
Klaus82 schrieb:
Eine schlichte Antwort ist einfach, dass ich die ganzen Methoden eines STL Containers nicht benötige.
Doch, das tust du. Der Container nimmt dir bspw. das Speichermanagement ab und du hast keine Probleme beim Kopieren. Zudem musst du nicht die Größe separat mitschleppen.
Klaus82 schrieb:
genauer einen Array mit Pointern auf solche Strukturen
Warum denn schon wieder Pointer?
BTW, Indexzugriff mit [] ist bei einem std::vector genauso möglich wie bei einem veralteten C-Array.
-
daddy_felix schrieb:
BTW, Indexzugriff mit [] ist bei einem std::vector genauso möglich wie bei einem veralteten C-Array.
veraltet, haha. ich hab's! nicht idiomatisch. ein anti-pattern.
-
Klaus82 schrieb:
Eine schlichte Antwort ist einfach, dass ich die ganzen Methoden eines STL Containers nicht benötige.
Zusätzlich zu dem, was daddy_felix schon sagte: Diese Aussage klingt so, als würdest du einen ungeheuren Overhead befürchten. Das ist nicht so,
std::vector
ist üblicherweise genau gleich schnell wie dynamisch angelegte Arrays. Je nach Operation sogar schneller, dank seiner Allokationsstrategie. Ausserdem erscheinen nicht aufgerufene Methoden nicht im Binary, sodass du nur für das bezahlst, was du wirklich brauchst.Klaus82 schrieb:
Damit ist die Verwendung eines STL Containers für mich 'mit Kanonen auf Spatzen schießen' in diesem Zusammenhang.
Ne. STL-Container sind der Standardansatz, wenn du dynamische Datenstrukturen benötigst. Wenn du eine selbstgestrickte Lösung oder direkt
new[]
unddelete[]
einsetzt, solltest du einen wirklich guten Grund dafür haben. Denn diese sind die Ausnahme, ihre Nachteile sollte man nicht leichtfertig in Kauf nehmen. Diese wären:- Manuelle Speicherverwaltung ist viel aufwändiger, fehleranfälliger und weniger übersichtlich als RAII.
- Du musst die Grösse separat speichern.
- Du kannst nicht leicht zwischen verschiedenen Containertypen wechseln, sollten sich die Anforderungen ändern.
- Sämtliche Operationen wie Einfügen, Löschen etc. müssen nachprogrammiert werden. Auch wenn du diese im Moment nicht brauchst, kann es plötzlich sein, dass du sehr froh um sie wärst. Mindestens ein Iterator-Interface sollte gegeben sein
- Du hast überhaupt keine Laufzeitchecks, was Bugs schwierig zu finden macht. Vernünftige STL-Implementierungen prüfen Indizes, Iteratoren etc. im Debug-Modus (d.h. kein Overhead) auf Gültigkeit, sodass entsprechende Fehler sofort entdeckt werden.
volkard schrieb:
veraltet, haha. ich hab's! nicht idiomatisch. ein anti-pattern.
Ja. Aber nicht wegen
std::vector
, sondernstd::array
:pAber ernsthaft, falls man C++11 verwenden kann, spricht wirklich nichts mehr für C-Arrays. Und selbst vorher ist
std::tr1::array
,boost::array
oder zur Not eine selbstgeschriebene Klasse sinnvoller.
-
So,
ich habe für die STL Befürworter was gebastelt.Ist das so gemeint bzw. besser?
Viele Grüße,
-- Klaus.// mb.cpp #include <iostream> #include <vector> struct foo { foo(unsigned int const); unsigned int const n; std::vector<double> daten; }; foo::foo(unsigned int const x): n(x) { for(unsigned int i = 0; i < n; ++i) daten.push_back(i*0.2); } struct bar { bar(std::vector<double>); std::vector<double> daten; }; bar::bar(std::vector<double> x): daten(x) {} int main() { unsigned const n = 10; struct foo f(n); struct bar b(f.daten); std::cout << "Time to clean up!" << std::endl; return 0; }
-
* zwei Anmerkungen: das "n" in "foo" brauchst du vermutlich nicht mehr
* dem Konstruktor von "bar" solltest du den vector als Referenz übergeben