for_each ausgabe einer klasse
-
Wo sind denn die Fachmänner die mir sagen können warum ich bei loadfromfile(..) ein stack overflow bekomme/verursache?
Wäre echt klasse wenn mir hier einer licht ins dunkle bringt.
-
Gustl schrieb:
Wo sind denn die Fachmänner die mir sagen können warum ich bei loadfromfile(..) ein stack overflow bekomme/verursache?
Ich habe dir schon vor zwei Seiten gesagt, du solltest einen Debugger benutzen. Solche Dinge sind eigentlich deine Aufgabe. Wenn wir nicht gerade etwas im Code sehen, können wir dir auch nicht helfen. Prüf doch die Variablen auf ihre Gültigkeit. Wenn du im Debug-Mode ausführst, sollte dir die STL bei Fehlern passende Assertions liefern.
for(Adresse tmp;f>>tmp;) liste.push_back(tmp);Das funktioniert wie jede For-Schleife. Zu Beginn wird ein
Adresse-Objekt namenstmperstellt. Während der Ausdruckf>>tmpwahr ist (solange eingelesen wird und der Stream sich in einem korrekten Zustand befindet), wird an die Liste angehängt. Hier würde ich allerdings eher eine While-Schleife nehmen. Wird die Schleife durchlaufen? Endet sie?
-
Dann komme ich hier leider nicht mehr weiter, trotzdem danke.
-
Bist Du sicher, dass der Code genauso aussieht wie Du oben sagst? Mich wundert ein bißchen, dass Du eine const-Referenz im operator>> hast.
-
Gustl schrieb:
Dann komme ich hier leider nicht mehr weiter, trotzdem danke.
Nette Einstellung. Erwartest du jetzt, dass wir den Code selber versuchen zu kompilieren - in der Hoffnung, dein Verhalten zu reproduzieren und den Fehler zu lokalisieren?
Wir haben dir schon viele Hinweise gegeben, um die du dich nicht sonderlich zu kümmern scheinst. Wenn du das mit dem Debugger nicht ernst nimmst, dann kommst du eben nicht weiter. Aber du kannst mir glauben, das war nicht das letzte Problem dieser Art. Von daher würde ich mir das mit dem Debugger nochmals überlegen. Wie gesagt wären auch Konsolenausgaben ein Anfang.
-
Jester schrieb:
Bist Du sicher, dass der Code genauso aussieht wie Du oben sagst? Mich wundert ein bißchen, dass Du eine const-Referenz im operator>> hast.
Ja, genauso sieht der code aus, asc hat mir empfohlen bei operator>> const zu verwenden, da diese Übergabeparameter ja auch nicht verändert werden, ist das wohl falsch?
Nexus schrieb:
Nette Einstellung. Erwartest du jetzt, dass wir den Code selber versuchen zu kompilieren - in der Hoffnung, dein Verhalten zu reproduzieren und den Fehler zu lokalisieren?
Wir haben dir schon viele Hinweise gegeben, um die du dich nicht sonderlich zu kümmern scheinst. Wenn du das mit dem Debugger nicht ernst nimmst, dann kommst du eben nicht weiter. Aber du kannst mir glauben, das war nicht das letzte Problem dieser Art. Von daher würde ich mir das mit dem Debugger nochmals überlegen. Wie gesagt wären auch Konsolenausgaben ein Anfang.
Nein, ich möchte nicht, dass ihr den Code selbst kompiliert und mir sagt wo der genaue Fehler liegt, oder vielleicht doch? Ich habe lediglich gefragt was an diesem code falsch sein soll, fakt ist das er beim operator >> den stack overflow bringt und zwar beim Laden von einer Datei.
Aber da nicht mal Spezialisten den genauen Fehler entdecken können, sehe ich für einen Anfänger wie mich auch nicht viel Land.Ich habe gestern bestimmt 2-3h probiert und gesucht wo der Hund begraben liegt, konnte jedoch keinen fehler im code finden, zudem der teil von operator >> überladen von asc kommt... genau dort ist dieser overflow... oder zumindest dort wo er versucht von der datei mit dem operator in ein objekt zu schreiben.
Somit wächst mir die ganze Sache über den Kopf und ich warf gestern die Flinte ins Korn.
Wenn mir hierbei keiner mehr helfen kann, werde ich die Datei zeilenweise auslesen und den string zum aufteilen an einer Funktion übergeben die eine Adresse zurückgibt, somit kommt es nicht mehr zum overflow, aber ich habe die aufgabe, mit den >> operator eine datei auszulesen, leider verfehlt.
Aber es funktioniert wenigstens.
Ich danke für eure Bemühungen und werde euch mit diesem Thema auch nicht mehr belästigen.
Ich habe in diesem Thema auch viel von euch gelernt. Danke.
MfG Gustl
-
Gustl schrieb:
Jester schrieb:
Bist Du sicher, dass der Code genauso aussieht wie Du oben sagst? Mich wundert ein bißchen, dass Du eine const-Referenz im operator>> hast.
Ja, genauso sieht der code aus, asc hat mir empfohlen bei operator>> const zu verwenden, da diese Übergabeparameter ja auch nicht verändert werden, ist das wohl falsch?
Es ist nicht nur falsch, sondern wäre auch gar nicht erst kompilierbar. Beim
operator <<ist die konstante Referenz korrekt, dort wird das Objekt schliesslich nicht verändert, aber beimoperator >>veränderst du das Objekt ja. Du liesst schliesslich in das Objekt ein. Daher ist es sehr wahrscheinlich, dass du uns nicht den richtigen Code gezeigt hast. Denn der gezeigte würde nicht kompilieren und daher wäre kein Stackoverflow möglich
Grüssli
-
@ Gustl:
Es ist mir bewusst, dass es sehr demotivierend sein kann, mehrere Stunden lang einen Fehler zu suchen. Es ist auch okay, dass du hier fragst, nur können wir dir auch beim besten Willen nicht mehr gross weiterhelfen. Dazu kommt, dass der Code mit grosser Wahrscheinlichkeit nicht dem entspricht, der den Fehler erzeugt (siehe Draveres Post). Vielleicht ist es auch möglich, dass mit dem Compiler etwas nicht stimmt.Das mit dem Debugger war nur ein guter Ratschlag, der kann dir Fehlersuche massiv vereinfachen.
-

genau das const muss hier an dieser Stelle raus... dann funktioniert es, auch ohne stack overflow.
Aber ich habe euch 100pro den Orginal code gezeigt und er komiliert auch mit den const beim operator >>... unglaublich aber wahr
Jetzt habt ihr mir doch geholfen.

Thx
MfG Gustl
-
In diesem Falle gibt es nur eins: Wechsle so schnell wie möglich den Compiler.
Dass er solch fehlerhaften Code zulässt und dadurch einen Laufzeitfehler erzeugt, kann es ja nicht sein.
-
hab visual studio 2005 V2.0
dachte eigentlich schon das der gut ist...
hats einer probiert zu kompilieren?
-
Gustl schrieb:
hab visual studio 2005 V2.0
dachte eigentlich schon das der gut ist...
Wäre mir auch nicht bekannt, dass der solche Bugs hat. Tritt der Fehler bei einer Const-Referenz auch in anderen Kontexten auf?
Ansonsten kann ich Microsoft Visual C++ 2008 Express empfehlen, diese IDE finde ich sehr gut.
-
Also an anderen stellen merkt der compiler das und gibt laut...
-
Hallo zusammen,
ein paar Bemerkungen zu der Diskussion:
1. const und warum compiliert es?
Gustl hatte auf Seite 3 dieses Themas noch folgende Funktion verwendet:
[cpp]ostream& operator<<(ostream& outstream, Adresse & const adresse) [/cpp]Eine Seite später war es dann diese Version:
[cpp]std::ostream& operator<<(std::ostream& outstream, Adresse const & adresse) std::istream& operator>>(std::istream& instream, Adresse const & adresse) [/cpp]Prüfe das nochmal genau, an welcher Stelle das "const" und wo das Ampersand steht. Denn:
[cpp] Adresse & const adresse <==> Adresse & adresse aber nicht gleich: Adresse const & adresse <==> const Adresse & adresse[/cpp]Im ersten Fall ist es eine konstante Referenz (was redundant ist, denn eine Referenz ist per definitionem konstant, daher sollte man das const auch weglassen) auf Objekt vom Typ Adresse. Im zweiten Fall ist es eine Referenz auf ein konstantes Objekt.
Der erste Fall dürfte ohne Probleme kompilieren, der zweite Fall natürlich nicht.
2. Container von Objekten vs. Container von Pointern
Meiner Erfahrung nach ist es besser, nicht die Objekte selbst im Container (list, deque...) zu speichern, sondern Pointer darauf. Ist sicher Ansichtssache, hat aber einige Vorteile:
1. Die Objekte werden nur einmal erzeugt und liegen dann auf dem Heap. Beim Einfügen in den Container wird nicht das gesamte Objekt KOPIERT, sondern nur 4 Bytes eingefügt.
2. Der Container selbst bleibt klein, da er ja nur den Pointer dazukriegt. Sollten also Elemente zum Container dazukommen, sich ändern oder gelöscht werden, kann die Operation schnell erfolgen, andernfalls muss nämlich der komplette Container (mit allen Objekten drin) im Speicher verschoben oder umorganisiert werden. Bei Pointern bleiben die Objekte, wo sie sind, und der Container bleibt klein.
3. Objekte werden genau dann erzeugt, wenn man sie braucht. Fixe Objekte wie Adresse1, Adresse2, Adresse3 sind ja später nicht vorgesehen, und auch der Code der Ladefunktion[cpp] for(Adresse tmp;f>>tmp;) liste.push_back(tmp);[/cpp]ergeugt ein Objekt zu viel (das letzte nämlich). Auch wenn es gleich wieder gelöscht wird, sollte das nicht geschehen.
Wie wäre es daher mit diesem:
[cpp]// in adressliste.h: typedef std::deque< Adresse* > liste_t; // nur ein anderer Name, leichter zu benutzen... liste_t liste; // adressliste.cpp: int Adressliste::loadfromfile( char const *dateiname ) { .... while( ! f.eof( ) ) { Adresse* pAdresse = new adresse; // <-- Das Objekt wird erst hier erzeugt f >> *pAdresse; liste.push_back( pAdresse ); }[/cpp]Jetzt werden exakt so viele Adressen erzeugt wie benötigt, und zwar schön kompakt alles innerhalb der while-Schleife. Später benutzt man die Liste wie gehabt, verwendet dann einfach -> statt . wenn man auf ein Element zugreifen will.
3. For each
Die for_each( ) -Schleife hat leider den Nachteil, dass ihre Benutzung so sehr anderes ist als die normale for( )-Schleife: Man braucht immer noch ein Funktionsobjekt, in dem die eigentliche Arbeit getan wird. Dadurch wird der Funktionsblock und der ausgeführte Code auseinandergerissen. Ich benutze daher gerne folgendes Makro (egal ob global oder lokal definiert). Wermutstropfen ist, dass wir den Typ des Containers angeben müssen, aber den haben wir ja vorher durch das typedef schon "klein" gemacht. ITER ist wie die Laufvariable in der for( )-Schleife, nur eben ein Iterator des Containers:
[cpp]#define FOREACH(ITER, CONTAINER, TYPE) for(TYPE::iterator ITER = CONTAINER.begin(); ITER != CONTAINER.end(); ++ITER)[/cpp]Die Ausgabemethode würde damit (und mit der Pointerliste aus Abschnitt 2) so ausschauen (beachte: wir brauchen den globalen Iterator it nicht mehr! Global ist sowieso zu vermeiden).:
[cpp]void Adressliste::ausgabe( ) { FOREACH( adresse; liste; liste_t ) { std::cout << *adresse; // Beachte: ein Iterator ist ein Pointer! } }[/cpp]Oder der Destruktor für die Adressenliste. Wir brauchen ihn, da der Standard-Destruktor nur die Liste selbst mit den Pointern löscht, nicht aber die Adressen, auf die die Pointer zeigen:
[cpp]Adressliste::~Adressliste( ) { FOREACH( adresse, liste, liste_t ) { delete *adresse; } }[/cpp]4. Große Rückgabewerte von Funktionen (Code von asc):
Deine Funktion AdressenEinlesen( ) hat als Rückgabewert die komplette Liste, die - neu - in der Datei erzeugt wird. Das hat einige Nachteile:
1. Bei der Rückgabe wird die gesamte Liste in die in main( ) erzeugte Liste KOPIERT. Das kann u.U. sehr aufwendig sein.
2. Was machst Du im Fall eines Fehlers, z.B. Datei ist nicht da?Es ist eleganter, die Liste nur einmal (in main) zu erzeugen und der Funktion AdressenEinlesen( ) als Referenz zu übergeben. Diese füllt dann die Adressen gleich in die Original-Liste ein (man könnte dann sogar an eine existierende Liste anhängen). Dann hast Du den Rüchgabewert frei für z.B. einen Fehlercode. Etwas so:
[cpp]int AdressenEinlesen( std::string const & dateiname, std::list<Adresse> & adressen ) { // 1. Datei Öffnen std::fstream datei(dateiname.c_str(), std::ios::in); if(datei.bad()) return -1; // <-- hier nur der Fehlercode // 2. Daten übertragen std::copy( std::istream_iterator<Adresse>(datei), // Vom Dateibegin... std::istream_iterator<Adresse>(), // ...bis kein Eintrag mehr existiert std::back_insert_iterator<std::list<Adresse> >(adressen)); // an die Liste hängen return 0; // <-- alles gutgegangen } // in main(): std::list<Adresse> adressen; int fehler = AdressenEinlesen("datei.txt", adressen); if( 0 != fehler ... [/cpp]Oder eben, wie es Gustl macht, dass das Einlesen eine Methode der Klasse Adressenliste ist...
-
O je, muss mich gleich verbessern, die Ausgabefunktion muss natürlich heißen:
[cpp]void Adressliste::ausgabe( ) { FOREACH( adresse; liste; liste_t ) { std::cout << *( *adresse ); } }[/cpp]Unser Iterator ist ja ein Pointer - auf einen Pointer - auf eine Adresse.
-
@minastaros,
Hast du getrunken? Nimmst du irgendwelche Drogen? Bist du ein Troll? Oder ist es dir mit dieser Argumentation wirklich ernst??????
Grüssli
-
@Dravere:
Das war gemein -.- Er hats bestimmt gut gemeint (scho ma nen Troll gesehen, der so viel Zeit investiert?) und mit Sicherheit eben auch einfach mal so beigebracht bekommen und seinem Lehrer/Dozent/... geglaubt...1. Bei der Rückgabe wird die gesamte Liste in die in main( ) erzeugte Liste KOPIERT. Das kann u.U. sehr aufwendig sein.
2. Was machst Du im Fall eines Fehlers, z.B. Datei ist nicht da?1. wird eh wegoptimiert...
mach mal das hier:std::list<Adresse> a; std::list<Adresse> b; for (size_t i = 0; i != 999; ++i) b.push_back (Adresse); std::cout << sizeof (a) << " == " << sizeof (b) << std::endl;wenn dich das wundert, dann kannst du dir mal die member von std::list / std::vector / std::deque / ... angucken...
außerdem hat die variante von asc nen weiteren Vorteil:const std::list<Adresse> adressen = AdressenEinlesen ("asd.txt"); //vs. std::list<Adresse> adressen; // kein const möglich :< int r = AdressenEinlesen ("asd.txt", adressen); //und mind. 4 Zeilen mehr + keine genaue Angabe, was passiert ist if (r) return;2. exception werfen
zur sache container <T> vs container <T*>:
container <T> ist sehr viel einfacher (schon das freigeben beim zerstören des objektes) - wenn man das umkopieren verhindern möchte, nimmt man std::list - wenn man die objekte beim kopieren iwie komisch behandelt werden sollen, spezialisiert man std::swap entsprechend...zu deinem makro:
void Adressliste::ausgabe( ) { FOREACH( adresse; liste; liste_t ) { std::cout << *( *adresse ); } }das sagt ja wohl schon alles... ~~
keine sau sieht durch... noch dazu würde man eine ausgabe() fkt const machen, dein makro verwendet aber iterator statt const_iterator - stinkt auch wieder
weiß auch nicht, in wie fern der compiler den fkt-aufruf bei dir jeden schleifen-durchlauf wegoptimieren kann und darf (du rufst unsinnigerweise jedes mal wieder end() auf...typedef std::vector<TkomischesObjekt> TadressContainer; void Adressliste::ausgabe( ) const { for (TadressContainer::const_iterator iter (adresse.begin()), end (adresse.end()); iter != end; ++iter ) { std::cout << *iter; } }Es ist einfach lesbarer, es ohne ptr zu machen, ist nicht so fehleranfällig (löschen eines objektes etc...) und es ist eben max. beim hinzufügen langsamer (glaube ich aber nicht so recht ^^) - wenn man ständig was hinzufügt und löscht, nimmt man eben ne deque, wenn nur einmal was hinzugefügt wird, dann vector und wenn man selten über alle elemente iterieren muss, dann nimmt man eben ne list - oder, wenn man häufig elemente aus der mitte löscht oder dort einfügen möchte... da gibts auch nen hübsches bild iwo im internet, wann welcher container zu nutzen ist - hab aber keine url, sondern mir das bild iwann ma gespeichert ^^
und um noch ma das bsp von oben zu verwenden:
void foo1 () { const std::list<Adresse> adressen = AdressenEinlesen ("asd.txt"); /*arbeit machen*/ } void foo2 () { const std::list<Adresse *> adressen = AdressenEinlesen ("asd.txt"); try { /*arbeit machen*/ } catch (...) { while (! adressen.empty()) delete adressen.pop_back(); //speichermanagement -.- throw; //und weiterwerfen } while (! adressen.empty()) delete adressen.pop_back(); } //wir müssen in AdressenEinlesen alle exceptions auffangen und Speicher freigeben und sie dann weiterwerfen void foo3 () { std::list<Adresse *> adressen; int r = AdressenEinlesen ("asd.txt", adressen); if (r) goto tidy_up_foo /*wie bekommt der aufrufer jz mit, dass was schief ging? -> wir bräuchten wieder nen rückgabewert - und um genau das zu verhindern, hat uns C++ exceptions gegeben*/ try { /*arbeit machen*/ } catch (...) { while (! adressen.empty()) delete adressen.pop_back(); throw; } tidy_up_foo: while (! adressen.empty()) delete adressen.pop_back(); } /*hier braucht man eigtl keine gotos, aber spätestens bei 2 oder 3 weiteren solchen Objekten würde ich endültig gotos nehmen, weil man sonst die hälfte des bildschirms einrücken müsste... es ist eben einfach die (für mich) am übersichtlichsten wirkende Methode um viele Fkt aufzurufen, die alle nen Rückgabewert haben und abhängig davon nen Objekt erzeugen,wo man sich um die Zerstörung kümmern muss*/Meinst du noch immer, dass deine Variante besser ist?
zu allerletzt:
ergeugt ein Objekt zu viel (das letzte nämlich). Auch wenn es gleich wieder gelöscht wird, sollte das nicht geschehen.
für genau so etwas ist der standard-ctor aber da... er sollte minimale ressourcen brauchen (da in 99% der fälle eh alles nur mit 0 initialisiert wird)... und wenn wir uns um EINEN solchen vorgang streiten... da sind selbst deine ständigen dereferenzierungen schlimmer (weil ihre häufigkeit eben linear ist - und nicht kostant(1) ).
bb
-
das sagt ja wohl schon alles... ~~
So etwas ähnliches macht durchaus Sinn. Bei komplizierten Konstrukten benutze ich noch gerne boost::foreach.
-
unskilled schrieb:
@Dravere:
Das war gemein -.- Er hats bestimmt gut gemeint (scho ma nen Troll gesehen, der so viel Zeit investiert?) und mit Sicherheit eben auch einfach mal so beigebracht bekommen und seinem Lehrer/Dozent/... geglaubt...Ja, ich habe schon Trolle gesehen, welche noch viel mehr Arbeit investiert haben.
Aber du hast womöglich trotzdem recht, dass es kein Trollversuch ist. Bei mir kam wohl die absolute Frustration durch. Schon wieder ein Neuling, der schon wieder nur Halbwahrheiten kennt und den man schon wieder aufklären muss. Wir machen hier eine absolute Sisyphosarbeit. Für jeden Neuling den wir belehren, kommen zwei dazu
@minastaros,
Möchte mich mal entschuldigen, falls das etwas beleidigend rüber kam.Nun aber zu deinem Text ...
minastaros schrieb:
1. const und warum compiliert es?
Im ersten Fall ist es eine konstante Referenz (was redundant ist, denn eine Referenz ist per definitionem konstant, daher sollte man das const auch weglassen) auf Objekt vom Typ Adresse. Im zweiten Fall ist es eine Referenz auf ein konstantes Objekt.Der erste Fall dürfte ohne Probleme kompilieren, der zweite Fall natürlich nicht.
Es ist nicht nur redundant, es ist falsch. Ohne Probleme kompilieren tut es auch nicht, bei meinem Kompiler wird eine Warnung geworfen. Es würde mich nicht erstaunen, wenn andere Kompiler sogar mit einem Fehler abbrechen.
Die Sache ist aber, dass Gustl nun schon mehrfach gesagt hat, dass der Code genau der gleiche wäre. Zudem wäre der Fehler nicht beim operator << aufgetaucht, sondern beim operator >>. Also geht die Argumentation in meinen Augen überhaupt nicht auf.
minastaros schrieb:
2. Container von Objekten vs. Container von Pointern
Meiner Erfahrung nach ist es besser, nicht die Objekte selbst im Container (list, deque...) zu speichern, sondern Pointer darauf. Ist sicher Ansichtssache, hat aber einige Vorteile:Ganz sarkastische Bemerkung:
Was für Erfahrungen sind das denn gewesen?minastaros schrieb:
1. Die Objekte werden nur einmal erzeugt und liegen dann auf dem Heap. Beim Einfügen in den Container wird nicht das gesamte Objekt KOPIERT, sondern nur 4 Bytes eingefügt.
Im Kontainer wird normalerweise der Defaultallokator verwendet, um Speicher für die Objekte zur reservieren. Dieser nutzt nichts anderes, als ein
new. Die Objekte im Kontainer liegen also genauso auf dem Heap.
Eine Kopie von 100 Bytes oder 4 Bytes ist übrigens meistens nicht so schlimm. Probleme tauchen meistens an ganz anderen Stellen auf. Das was du machst, nennt man premature optimization. Man probiert die Laufzeit des Programmes zu verbessen, ohne zu wissen wo die Problemstellen tatsächlich sind. Das führt meistens zu sehr schlechtem Code.minastaros schrieb:
2. Der Container selbst bleibt klein, da er ja nur den Pointer dazukriegt. Sollten also Elemente zum Container dazukommen, sich ändern oder gelöscht werden, kann die Operation schnell erfolgen, andernfalls muss nämlich der komplette Container (mit allen Objekten drin) im Speicher verschoben oder umorganisiert werden. Bei Pointern bleiben die Objekte, wo sie sind, und der Container bleibt klein.
Dieser Punkt ist doppelt verkehrt. Da die Objekte im Kontainer über den Allokator auf dem Heap landen, verändert sich die Grösse des Kontainerobjektes überhaupt nicht.
Zudem müssen nicht immer alle Objekt im Kontainer neuorganisiert werden, wenn ein neues Objekt dazukommt. Die Kontainer haben da ganz unterschiedliche Techniken. Einstd::vectorreserviert Speicher im voraus, um die Anzahl Kopiervorgänge zu verringern. Einestd::dequearbeitet mit Speichersegmenten. Einestd::listbraucht normalerweise sogar gar keine Kopie mehr, weil die Objekte einfach umgehängt werden. Einestd::map,std::set,std::multimapundstd::multisethaben intern einen Baum. Dieser arbeitet mit Nodes wie einestd::list. Die Objekte werden somit auch einfach nur umgehängt.Ergo -> Du solltest dich mal ein wenig einlesen in den Bereich von Kontainern. Wir haben glaub ich sogar einen Artikel im Magazin.
minastaros schrieb:
3. Objekte werden genau dann erzeugt, wenn man sie braucht. Fixe Objekte wie Adresse1, Adresse2, Adresse3 sind ja später nicht vorgesehen, und auch der Code der Ladefunktion
[cpp] for(Adresse tmp;f>>tmp;) liste.push_back(tmp);[/cpp]ergeugt ein Objekt zu viel (das letzte nämlich). Auch wenn es gleich wieder gelöscht wird, sollte das nicht geschehen.
Das Objekt
tmpwird genau nur ein einziges Mal erstellt, nämlich am Anfang der for-Schleife. Danach wird jeweils in dieses Objekt eingelesen und das Objekt in die Liste kopiert. Es wird nie neu erstellt. Man hat ein einzelnes zusätzliche Objekt, welches sich auf dem sehr schnellen Stack befindet. Es wäre sogar möglich, dass der Kompiler es schafft, dieses Objekt gänzlich wegzuoptimieren. Mit so einem Vorgehen gibt es überhaupt gar keine Nachteile.minastaros schrieb:
.... while( ! f.eof( ) ) { Adresse* pAdresse = new adresse; // <-- Das Objekt wird erst hier erzeugt f >> *pAdresse; liste.push_back( pAdresse ); }Und WER gibt die Objekte wieder frei? Wem gehören die Objekte? Du musst hier ein zusätzliches Speichermanagement einbauen, welches nur stört. Zudem verbraucht deine Lösung sogar noch mehr Speicher und könnte gar noch langsamer sein. Wieso? Diese kleinen 4 oder 8 Byte Adressen werden zusätzlich gespeichert. Und im Kontainer wird dazu meistens der Defaultallokator verwendet. Dieser ist aber meistens nicht sehr optimal, um kleine Objekte zu allokieren und verhält sich daher oft eher langsam.
minastaros schrieb:
3. For each
Die for_each( ) -Schleife hat leider den Nachteil, dass ihre Benutzung so sehr anderes ist als die normale for( )-Schleife: Man braucht immer noch ein Funktionsobjekt, in dem die eigentliche Arbeit getan wird. Dadurch wird der Funktionsblock und der ausgeführte Code auseinandergerissen.Vor allem der letzte Satz beweist mir eindeutig, dass du die folgenden Dinge nicht kennst:
<functional>
Boost.Bind, bzw. std::tr1::bind, bzw. C++0x std::bind
Boost.Lambda, bzw. C++0x Lambda-ExpressionsDes Weiteren ist so eine Aufteilung teilweise wirklich gewünscht. Wenn ein Funktor nämlich eine allgemeingültige Aufgabe erledigen muss, eine Aufgabe, welche man an verschiedenen Stellen einsetzt mit verschiedenen Kontainer, ist das durchaus eine sinnvolle Sache. Sonst müsstest du dein FOREACH überall erneut hinschreiben müssen -> Menge an Codeduplizierung.
Oder du lagerst die sache in eine Funktion aus, dann hast du aber nicht viel mehr als du mit einem Funktor hättest.minastaros schrieb:
(beachte: wir brauchen den globalen Iterator it nicht mehr! Global ist sowieso zu vermeiden).:
Diese Aussage finde ich aber der absolute Knüller. Was für einen globales Iterator Objekt? Seit wann braucht ein
std::for_eachein globales Iterator Objekt? So ein UNSINN!minastaros schrieb:
Oder der Destruktor für die Adressenliste. Wir brauchen ihn, da der Standard-Destruktor nur die Liste selbst mit den Pointern löscht, nicht aber die Adressen, auf die die Pointer zeigen:
Den du gar nicht erst benötigen würdest, wenn du die Objekte, statt den Zeiger speichern würdest.
minastaros schrieb:
4. Große Rückgabewerte von Funktionen (Code von asc):
Deine Funktion AdressenEinlesen( ) hat als Rückgabewert die komplette Liste, die - neu - in der Datei erzeugt wird. Das hat einige Nachteile:
1. Bei der Rückgabe wird die gesamte Liste in die in main( ) erzeugte Liste KOPIERT. Das kann u.U. sehr aufwendig sein.minastaros schrieb:
Es ist eleganter, die Liste nur einmal (in main) zu erzeugen und der Funktion AdressenEinlesen( ) als Referenz zu übergeben. Diese füllt dann die Adressen gleich in die Original-Liste ein (man könnte dann sogar an eine existierende Liste anhängen).
Da gäbe ich dir recht. Das ist der einzige Punkt, wo ich das tue ^^
Allerdings möchte ich noch etwas hervorheben, was du geschrieben hast, was aber beinahe droht unter zu gehen:
Das kann u.U. sehr aufwendig sein.
!Unter Umständen!
Und meistens kann der Programmierer diese Umstände schlecht abschätzen. Denn "Unter Umständen" kann der Kompiler solche Kopien effizient wegoptimieren.minastaros schrieb:
2. Was machst Du im Fall eines Fehlers, z.B. Datei ist nicht da?
Wenn du nun den Rückgabewert der Funktion als Fehlermeldung missbrauchen willst, dann empfehle ich dir dringend die folgende Lektüre:
http://magazin.c-plusplus.net/artikel/Exception-Handling
http://magazin.c-plusplus.net/artikel/Modernes Exception-Handling Teil 1 - Die Grundlagen
http://magazin.c-plusplus.net/artikel/Modernes Exception-Handling Teil 2 - Hinter den Kulissenminastaros schrieb:
Dann hast Du den Rüchgabewert frei für z.B. einen Fehlercode.
TATSÄCHLICH ... da steht dieser Unsinn. Lies unbedingt die Links im Magazin, welche ich oben hingeschrieben habe.
drakon schrieb:
So etwas ähnliches macht durchaus Sinn. Bei komplizierten Konstrukten benutze ich noch gerne boost::foreach.
Ja, man kann so ein
FOREACHtatsächlich manchmal anwenden. Aber Boost.Foreach ist nicht als Ersatz fürstd::for_eachgedacht, so wie es minastaros aber vorgeschlagen hat.Grüssli
-
Mal locker bleiben.
Dravere, war nicht nett, aber Entschuldigung akzeptiert.
Container
Selbstverständlich haben sie unterschiedliche Techniken. Gustl hat nun aber eine Deque verwendet, und diese speichert Objekte hintereinander ab und nicht auf dem Heap (dort liegen allenfalls die Speicherseiten). Über eine Deque kann man schön iterieren und einfach neue Elemente anfügen, zum Sortieren oder In-der-Mitte-Einfügen (was bei einer Adressenverwaltung nicht unüblich ist) müssen jedoch die Elemente selbst umkopiert werden. Und dann macht es - je nach Anzahl der Objekte und deren Komplexität - durchaus einen Unterschied zu Pointern.
Dass die Objekte mitsamt ihren strings (die std-string-Objekte; die c-Strings liegen natürlich woanders) hintereinander im Speicher liegen, zeigt folgendes Programm:
[cpp]#include <deque> class Address { public: std::string a; std::string b; std::string c; std::string d; std::string e; int i; Address( std::string, std::string, std::string, std::string, std::string, int ); }; Address::Address( std::string a_, std::string b_, std::string c_, std::string d_, std::string e_, int i_ ) : a( a_ ) , b( b_ ) , c( c_ ) , d( d_ ) , e( e_ ) , i( i_ ) { } typedef std::deque< Address > deq_t; int main() { deq_t deq; for( int i = 0; i < 5; ++i ) { Address* adr = new Address( "123", "456", "789", "abcd", "defg", i ); deq.push_back( *adr ); delete adr; } for( int i = 0; i < 5; ++i ) { std::cout << "Adresse von Objekt [" << i << "], id " << deq[ i ].i << " : " << &( deq[ i ] ) << std::endl; } deq_t::iterator it = deq.begin( ); ++it; ++it; deq.erase( it ); // Löscht Element aus der Mitte std::cout << "Nach dem Löschen:" << endl; for( int i = 0; i < 4; ++i ) { std::cout << "Adresse von Objekt [" << i << "], id " << deq[ i ].i << " : " << &( deq[ i ] ) << std::endl; } return 0; } [/cpp]Bei mir liefert es folgende Ausgabe:
Adresse von Objekt [0], id 0 : 0x8053120 Adresse von Objekt [1], id 1 : 0x8053138 Adresse von Objekt [2], id 2 : 0x8053150 Adresse von Objekt [3], id 3 : 0x8053168 Adresse von Objekt [4], id 4 : 0x8053180 Nach dem Löschen: Adresse von Objekt [0], id 0 : 0x8053120 Adresse von Objekt [1], id 1 : 0x8053138 Adresse von Objekt [2], id 3 : 0x8053150 Adresse von Objekt [3], id 4 : 0x8053168Jedes Objekt hat demnach 24 Bytes, für jedes Member 4; je mehr Member ein Objekt hat, desto größer ist es auch in der Deque. Man sieht auch, dass die Objekte beim Entfernen eines Elements verschoben werden, ebenso wohl auch beim Sortieren.
Sarkastische Erfahrung: Es ging um Sensor-Objekte mit zahlreichen Parametern, die dynamisch erzeugt und entfernt werden mussten. In einem Fall auch um verschiedene abgeleitete Typen, die zusammen verwaltet wurden. Das ging dann nur noch über Pointer auf Basisklasse.
Im Übrigen hatte ich geschrieben, dass man es mit Pointern machen kann, aber man muss es nicht. Kommt eben auf das Projekt an.
Vor allem der letzte Satz beweist mir eindeutig, dass du die folgenden Dinge nicht kennst:
Verschätze dich nicht.
Hab davon gehört, aber muss ich jede Bibliothek einsetzen, nur weil sie existiert? Klar kann man. Nur, bei Embedded-Geräten schaut die Welt etwas anders aus. Wozu also eine neue Bibliothek, wenn mir mein Makro dafür völlig reicht.
FOREACH
Danke unskilled für den Tip mit dem end()-Aufruf. Lässt sich aber auch vermeiden, und das Ganze geht natürlich auch mit const:
[cpp]#define CFOREACH(ITER, CONTAINER, TYPE) for(TYPE::const_iterator ITER = CONTAINER.begin( ), ENDITER = CONTAINER.end( ); ITER != ENDITER; ++ITER)[/cpp]Ob da "keine" Sau mehr durchblickt, wenn da "FOREACH" steht, mag jeder selbst entscheiden. Leute, wenn es jemand mag, weil er nicht jedes Mal die komplette for-Schleife schreiben will, soll er es machen, wenn nicht, dann eben nicht.
A propos: Der Aufruf muss selbstverständlich mit Kommas sein.
Globaler Iterator it: Genau diesen hatte Gustl in seinen Funktionen savetofile() und ausgabe() verwendet.
Ob die For(each)-Schleife zusammen mit dem, was sie tun soll, sein soll oder getrennt, ist doch wieder eine Frage der Aufgabe und des Geschmacks. In Gustls Programm waren es die beiden Funktionen console() und ausgabe() oder savetofile() und elementspeichern(), die man so eben zusammenfassen kann. KANN.
Exceptions: Ja, sind eine tolle Sache. Aber sie sollten auch nicht für jeden x-beliebigen Fehler eingesetzt werden. "Fehler" kann als Rückgabewert für eine Datei-Zugriffs-Funktion recht weit verstanden werden, sagen wir besser "Status" oder "Ergebnis". Ob nun if() oder try()/catch() das Problem besser löst, kommt doch immer auf den Anwendungsfall an. Google schreibt in seinen Open-Source-Programmierrichtlinen sogar vor: "We do not use C++ exceptions." Ich verwende sie, aber nur für bestimmte Arten von Fehlern.
Jedenfalls würde ich nicht eine komplette Liste als Rückabewert übergeben. Nur mal fiktiv: Zur Ladefunktion möchte jemand auch eine Funktion haben, die Datensätze aus einer Datei an eine bestehende Liste anhängt. Das geht dann nicht mehr als Rückgabewert. Wenn man mit Referenzen arbeitet, könnten laden() und anhängen() zumindest eine gleichartige Signatur haben.