[C++] Try/Catch -> Objekt anlegen
[C++] Try/Catch -> Objekt anlegen
Ich bin gerade auf ein "Problem" gestoßen, bei dem es mich wundert, dass ich noch nie drüber gestolpert bin. Ein try-Block erstellt ja logischerweise ein eigenes Scope. Wenn ich darin nun ein Objekt anlege, lebt es erstens nur bis zum Ende des Scopes und zweitens ist es außerhalb des Scopes nicht referenzierbar. Normalerweise kann man sich nun helfen, indem man vorm Scope das Objekt anlegt und dann im try-Block eine "Init-Methode" aufruft oder einen Pointer vorm Scope anlegt und erstmal nullt und dann im Block initialisiert. Oder man packt alles was man mit dem Objekt anstellt mit in den try-Block und begrenzt die Lebenszeit des Objekts gezielt mit diesem Block.
Das alles ist aber irgendwie unschön. Gibt es eigentlich gute Gründe, dass man try nicht direkt auf einen Einzelausdruck anwenden kann, ohne das Scope zu ändern? ich bin kein Freund davon einen Pointer anzulegen, nur um Exceptions abzufangen. Und ich mag auch keine ellenlangen try-Blöcke nur um alles mitzunehmen, was das lokale Objekt benutzt. Ich könnte zwar kleinere try-Blöcke in einem großen kapseln, aber das kann doch auch nicht die Lösung sein.
Wie handhabt ihr das? Ich denke gerade an std::unique_ptr, aber ich weiß nicht so recht. :(
Das alles ist aber irgendwie unschön. Gibt es eigentlich gute Gründe, dass man try nicht direkt auf einen Einzelausdruck anwenden kann, ohne das Scope zu ändern? ich bin kein Freund davon einen Pointer anzulegen, nur um Exceptions abzufangen. Und ich mag auch keine ellenlangen try-Blöcke nur um alles mitzunehmen, was das lokale Objekt benutzt. Ich könnte zwar kleinere try-Blöcke in einem großen kapseln, aber das kann doch auch nicht die Lösung sein.
Wie handhabt ihr das? Ich denke gerade an std::unique_ptr, aber ich weiß nicht so recht. :(
Ohne Input kein Output.
- dot
- Establishment
- Beiträge: 1746
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Die erste Frage ist: Wieso brauchst du überhaupt try Blöcke um auf alle möglichen Exceptions abzuchecken? Imo sollte das schwer zu denken geben...
Abgesehen davon, frage ich mich, wieso genau du einen try-Block ausschließlich auf die Initialisierung eines Objektes beschränken musst; irgendwie kommt mir das merkwürdig vor. try ist rein konzeptionell an einen Scope gebunden. Was machst du denn, wenn die Initialisierung eine Exception wirft und das Objekt somit nicht erzeugt wurde. Du musst den aktuellen Scope verlassen, denn ansonsten hast du den Fall, dass ein Name im Scope ist, der an ein Objekt gebunden ist, das nicht existieren kann, womit wir uns in einen flotten Widerspruch verhäddert haben...
Abgesehen davon, frage ich mich, wieso genau du einen try-Block ausschließlich auf die Initialisierung eines Objektes beschränken musst; irgendwie kommt mir das merkwürdig vor. try ist rein konzeptionell an einen Scope gebunden. Was machst du denn, wenn die Initialisierung eine Exception wirft und das Objekt somit nicht erzeugt wurde. Du musst den aktuellen Scope verlassen, denn ansonsten hast du den Fall, dass ein Name im Scope ist, der an ein Objekt gebunden ist, das nicht existieren kann, womit wir uns in einen flotten Widerspruch verhäddert haben...
Re: [C++] Try/Catch -> Objekt anlegen
Zu deiner ersten Frage: Es geht hier um die Initialisierung meiner Applikation. Da wird viel erzeugt und ich prüfe ob die Objekte korrekt erstellt werden. Generell verwende ich aber meist Exception wenn etwas passiert, was nicht passieren sollte.
Danke für deine Antwort. Der Satz "Was machst du denn, wenn die Initialisierung eine Exception wirft und das Objekt somit nicht erzeugt wurde." war genau der richtige Denkanstoß. Ich wollte anfangs antworten "Ich beende das Programm oder verlasse die aktuelle Funktion.". Aber natürlich muss man das nicht tun. Man kann im catch-Block ja sonstwas machen und danach wäre das Verhalten tatsächlich undefiniert, wenn das Objekt gar nicht erstellt wurde. Von daher ist das ganze doch sinnvoll. Du hast also meine Frage beantwortet, ob es gute Gründe dafür gibt und nun sind sie mir natürlich klar.
Also vielen Dank. ;)
Danke für deine Antwort. Der Satz "Was machst du denn, wenn die Initialisierung eine Exception wirft und das Objekt somit nicht erzeugt wurde." war genau der richtige Denkanstoß. Ich wollte anfangs antworten "Ich beende das Programm oder verlasse die aktuelle Funktion.". Aber natürlich muss man das nicht tun. Man kann im catch-Block ja sonstwas machen und danach wäre das Verhalten tatsächlich undefiniert, wenn das Objekt gar nicht erstellt wurde. Von daher ist das ganze doch sinnvoll. Du hast also meine Frage beantwortet, ob es gute Gründe dafür gibt und nun sind sie mir natürlich klar.
Also vielen Dank. ;)
Ohne Input kein Output.
Re: [C++] Try/Catch -> Objekt anlegen
Naja das ist schon ein Problem, das BeRsErKeR da beschreibt. Und es gibt schlicht und einfach keine gute Lösung. Exceptions und konsequentes RAII haben halt nicht nur Vorteile. Meiner Meinung nach sogar mehr Nachteile als Vorteile. Ich würde Exceptions vermeiden, wenn es geht.
- Krishty
- Establishment
- Beiträge: 8342
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Bei mir wird nach der Initialisierung alles freigegeben, was ich nicht zum Ausführen brauche. Geht natürlich nur mit manueller Speicherverwaltung; RAII ist dabei wirklich die Pest.
auto toInitData = new InitData();
Game game(toInitData);
delete toInitData;
game.run();
auto toInitData = new InitData();
Game game(toInitData);
delete toInitData;
game.run();
- dot
- Establishment
- Beiträge: 1746
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Wenn du Objekte hast, die nur während der Konstruktion eines anderen Objektes existieren sollen, wieso werden die nicht lokal im Konstruktor erzeugt?Krishty hat geschrieben:Bei mir wird nach der Initialisierung alles freigegeben, was ich nicht zum Ausführen brauche. Geht natürlich nur mit manueller Speicherverwaltung; RAII ist dabei wirklich die Pest.
Oder aber auch einfach so:
Code: Alles auswählen
Game game(InitData());
game.run();
- Krishty
- Establishment
- Beiträge: 8342
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Ich weiß dummerweise erst nach Laden der Daten, welches von drei Spielen gestartet werden soll. Außerdem würde der Compiler dann sizeof(InitData) + sizeof(Game) an Stapelspeicher verbrauchen (was bei mir wirklich zum Problem wurde).
- dot
- Establishment
- Beiträge: 1746
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Und wo findet die Entscheidung statt? Mit Code wie dem obigen funktioniert das dann ja genauso nicht!?Krishty hat geschrieben:Ich weiß dummerweise erst nach Laden der Daten, welches von drei Spielen gestartet werden soll.
Na dann haltKrishty hat geschrieben:Außerdem würde der Compiler dann sizeof(InitData) + sizeof(Game) an Stapelspeicher verbrauchen (was bei mir wirklich zum Problem wurde).
Code: Alles auswählen
Game game(std::make_unique<InitData>().get());
game.run();
- Krishty
- Establishment
- Beiträge: 8342
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Im Quelltext oben war die Entscheidung natürlich nicht drin; nein. Die Entscheidung findet statt, sobald die Archive, in denen das Spiel steckt, geladen wurden. Anhand der Dateiversionen kann ich sehen, welches Spiel gestartet werden soll.
Jetzt sind die Quelldateien immernoch allokiert wenn das Spiel startet.dot hat geschrieben:Na dann halt
oder noch besser mit sowas wie einem scoped_ptr...Code: Alles auswählen
Game game(std::make_unique<InitData>().get()); game.run();
- Krishty
- Establishment
- Beiträge: 8342
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Im Quelltext oben war die Entscheidung natürlich nicht drin; nein. Die Entscheidung findet statt, sobald die Archive, in denen das Spiel steckt, geladen wurden. Anhand der Dateiversionen kann ich sehen, welches Spiel gestartet werden soll.
auto toInitData = new InitData();
if(a == toInitData->version()) {
GameA game(toInitData);
delete toInitData;
game.run();
} else if(b == toInitData->version()) {
GameB game(toInitData);
delete toInitData;
game.run();
} else {
Editor editor(toInitData);
editor.run();
}
auto toInitData = new InitData();
if(a == toInitData->version()) {
GameA game(toInitData);
delete toInitData;
game.run();
} else if(b == toInitData->version()) {
GameB game(toInitData);
delete toInitData;
game.run();
} else {
Editor editor(toInitData);
editor.run();
}
Jetzt sind die Quelldateien immernoch allokiert wenn das Spiel startet.dot hat geschrieben:Na dann halt
oder noch besser mit sowas wie einem scoped_ptr...Code: Alles auswählen
Game game(std::make_unique<InitData>().get()); game.run();
- dot
- Establishment
- Beiträge: 1746
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
So etwas zu bauen, gibt mir prinzipiell ein flaues Gefühl im Magen; aber die einfachste Lösung wäre wohl:Krishty hat geschrieben:Im Quelltext oben war die Entscheidung natürlich nicht drin; nein. Die Entscheidung findet statt, sobald die Archive, in denen das Spiel steckt, geladen wurden. Anhand der Dateiversionen kann ich sehen, welches Spiel gestartet werden soll.
auto toInitData = new InitData();
if(a == toInitData->version()) {
GameA game(toInitData);
delete toInitData;
game.run();
} else if(b == toInitData->version()) {
GameB game(toInitData);
delete toInitData;
game.run();
} else {
Editor editor(toInitData);
editor.run();
}
Code: Alles auswählen
auto toInitData = make_unique<InitData>();
if(a == toInitData->version()) {
GameA game(toInitData.release());
game.run();
} else if(b == toInitData->version()) {
GameB game(toInitData.release());
game.run();
} else {
Editor editor(toInitData.get());
editor.run();
}
Nope, das Game bekommt einfach nur einen InitData*; nach Auswertung des vollen Ausdrucks wird die InitData mit dem temporären unique_ptr ordnungsgemäß zerstört...Krishty hat geschrieben:Jetzt sind die Quelldateien immernoch allokiert wenn das Spiel startet.dot hat geschrieben:Na dann halt
oder noch besser mit sowas wie einem scoped_ptr...Code: Alles auswählen
Game game(std::make_unique<InitData>().get()); game.run();
- Krishty
- Establishment
- Beiträge: 8342
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Stimmt; das war ein Brainfart :oops:dot hat geschrieben:Nope, das Game bekommt einfach nur einen InitData*; nach Auswertung des vollen Ausdrucks wird die InitData mit dem temporären unique_ptr ordnungsgemäß zerstört...Krishty hat geschrieben:Jetzt sind die Quelldateien immernoch allokiert wenn das Spiel startet.
Jetzt sind die Quelldateien aber immernoch allokiert wenn das Spiel startet, oder? (Zumindest sagt dieses cppreference-Beispiel, dass man nach release() explizit deleten muss.)dot hat geschrieben:Code: Alles auswählen
auto toInitData = make_unique<InitData>(); if(a == toInitData->version()) { GameA game(toInitData.release()); game.run(); } else if(b == toInitData->version()) { GameB game(toInitData.release()); game.run(); } else { Editor editor(toInitData.get()); editor.run(); }
- dot
- Establishment
- Beiträge: 1746
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Sry, das stimmt natürlich, muss man natürlich reset() verwenden... :oops:Krishty hat geschrieben:Jetzt sind die Quelldateien aber immernoch allokiert wenn das Spiel startet, oder? (Zumindest sagt dieses cppreference-Beispiel, dass man nach release() explizit deleten muss.)
- Krishty
- Establishment
- Beiträge: 8342
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Sooo; und das ist das Problem mit RAII. Die Ressourcen werden nicht immer stapelmäßig aufgebaut, und wenn man dann RAII reinzuquetschen versucht, versteht man den Quelltext am Ende noch weniger.
- dot
- Establishment
- Beiträge: 1746
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Die RAII Variante mit unique_ptr und reset ist praktisch isomorph zur Variante mit new und delete, mit dem Unterschied, dass die RAII Variante auch noch exception-safe ist!?
Zuletzt geändert von dot am 26.09.2014, 13:39, insgesamt 6-mal geändert.
- dot
- Establishment
- Beiträge: 1746
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Und genau dort kommt move Semantik ins Spiel...Krishty hat geschrieben:Sooo; und das ist das Problem mit RAII. Die Ressourcen werden nicht immer stapelmäßig aufgebaut, [...]
Besser wär es wohl, wenn die InitData per move an das jeweilige Objekt übertragen wird. Dieses kann dann entscheiden, ob die Daten weiter benötigt werden, oder ob sie freigegeben werden können:
Code: Alles auswählen
class GameA
{
...
GameA(std::unique_ptr<InitData> init_data) { // ... }
};
class GameB
{
...
GameB(std::unique_ptr<InitData> init_data) { // ... }
};
class Editor
{
...
GameA(std::unique_ptr<InitData>&& init_data) { // she's a keeper ... }
};
Eine objektive Betrachtung mag hier schwer bis unmöglich sein, aber dem Artikel kann ich ausnahmsweise ( :mrgreen: ) absolut nicht zustimmen. Was genau soll in den Beispielen dort illustriert werden? Dass es schwer ist, ohne RAII exception-safe code zu schreiben!? bummer...Krishty hat geschrieben:Und schwerer zu verstehen.
Bei Verwendung von RAII würde im "problematischen" Code an der entsprechenden Stelle sofort ein Laufzeitfehler auftreten. Und willst du mir wirklich erzählen, dass die halbgare C-Lösung lesbarer ist, als die OOP Variante?
Edit: Das mit dem Laufzeitfehler bekommt man natürlich nur hin, wenn man es ordentlich macht und nicht diesem kaputten, Property-basierten Programmierstil folgt...
- Krishty
- Establishment
- Beiträge: 8342
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Ja genau. Wir töten die Problematik durch noch mehr Komplexität! :)dot hat geschrieben:Und genau dort kommt move Semantik ins Spiel...Krishty hat geschrieben:Sooo; und das ist das Problem mit RAII. Die Ressourcen werden nicht immer stapelmäßig aufgebaut, [...]
Zeig den Quelltext mal jemandem, der kein C++-Experte ist. Du wirst Stunden brauchen um zu vermitteln, was ein unique_ptr tut, warum man den an der Stelle überhaupt braucht, und, warum da „ein logisches UND“ hinter dem Parameter steht. Und da bist du nicht einmal an dem Punkt, wo eine Ausnahme fliegen soll.
new und delete kapieren sie in zwei Minuten. Verschachtelte if(NULL == können sie in Python oder Java in Sekunden selber nachbauen; das musst du nicht einmal erklären.
Und diese Leute kümmern sich halt später um dein Projekt – was schon empirisch einleuchten sollte, weil es von denen viel mehr gibt als von denen, die alle boost-Zeigertypen auswendig aufzählen können. Darum weg mit Smart Pointern, Move Semantics, Exceptions, und RAII. Nichts davon macht irgendwas leichter wartbar. Es ist bloß kognitive Last, die nebenbei noch alles aufbläht damit die Leute sich schneller einen neuen Computer kaufen müssen.
- dot
- Establishment
- Beiträge: 1746
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Wunderbar und dann können sie superschnell und effizient falschen Code schreiben, der gerade hinreichend richtig aussieht, dass es keinem auffallen muss...Krishty hat geschrieben:new und delete kapieren sie in zwei Minuten. Verschachtelte if(NULL == können sie in Python oder Java in Sekunden selber nachbauen; das musst du nicht einmal erklären.
Seh ich anders. Die Komplexität hier ist dem zu lösenden Problem inhärent. Was hast du davon, wenn jemand simplen Code schreibt, der die komplexe Hälfte des eigentlichen Problems (Kontrollfluss im Fehlerfall) ignoriert? Abgesehen davon sollte korrekte Verwendung von RAII keinen Overhead im Vergleich zu äquivalenten Lösung in deinem simplified C++ verursachen (sofern eine solche existiert; hatten wir nicht gerade unlängst irgendwo ein Beispiel, das ohne RAII rein prinzipiell gar nicht korrekt lösbar war!?)...Krishty hat geschrieben:Es ist bloß kognitive Last, die nebenbei noch alles aufbläht damit die Leute sich schneller einen neuen Computer kaufen müssen.
-
- Establishment
- Beiträge: 426
- Registriert: 23.01.2013, 15:55
Re: [C++] Try/Catch -> Objekt anlegen
Exceptions sind kompliziert. Ohne Exception-Support der Sprache allerdings noch mehr.
Smart Pointer haben zum Beispiel als "std::unique_ptr" keinen Overhead.
Bei RAII sehe ich auch keinen Grund für eine Effizienzverringerung, schließlich ist es bloß ein Konzept zur Objektzerstörung/-konstruktion.
Bei Exception scheiden sich die Gemüter. Es kommt wohl auf den Anwendungsfall an, aber angeblichwird zum Beispiel bei x64 auf Windows kein Laufzeit Performanceoverhead generiert(das müsstest du aber sowieso besser wissen), bzw. wären die notwendigen Verzweigungen und die Behandlung aller Situationen auch nicht ganz umsonst.
Die Stunden die Grundkonzepte einer Sprache zu erklären, würde ich auf jeden Fall unbedingt aufbringen. Ansonsten ist es nicht zeitgemäßes C++ sondern "C mit Objekten" das ganz bestimmt bei konkreten Problemen nicht einfacher sondern umständlicher ist.
Move Semantic erhöht anscheinend nachweislich in einiger Software die Effizienz merklich.Darum weg mit Smart Pointern, Move Semantics, Exceptions, und RAII. Nichts davon macht irgendwas leichter wartbar. [...] damit die Leute sich schneller einen neuen Computer kaufen müssen.
Smart Pointer haben zum Beispiel als "std::unique_ptr" keinen Overhead.
Bei RAII sehe ich auch keinen Grund für eine Effizienzverringerung, schließlich ist es bloß ein Konzept zur Objektzerstörung/-konstruktion.
Bei Exception scheiden sich die Gemüter. Es kommt wohl auf den Anwendungsfall an, aber angeblichwird zum Beispiel bei x64 auf Windows kein Laufzeit Performanceoverhead generiert(das müsstest du aber sowieso besser wissen), bzw. wären die notwendigen Verzweigungen und die Behandlung aller Situationen auch nicht ganz umsonst.
Die Stunden die Grundkonzepte einer Sprache zu erklären, würde ich auf jeden Fall unbedingt aufbringen. Ansonsten ist es nicht zeitgemäßes C++ sondern "C mit Objekten" das ganz bestimmt bei konkreten Problemen nicht einfacher sondern umständlicher ist.
- Krishty
- Establishment
- Beiträge: 8342
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Was genau so auf State-of-the-Art C++’11-RAII-Quelltext zutrifft. Nur, dass dabei der Satz Regeln, den man zum richtigen Verstehen anwenden muss, ungleich größer ist, und die Fehler viel subtiler. Wenn du jemandem (inklusive mir) ein Stück Quelltext zeigst und fragst, was der Fehler ist, wirddot hat geschrieben:Wunderbar und dann können sie superschnell und effizient falschen Code schreiben, der gerade hinreichend richtig aussieht, dass es keinem auffallen muss...Krishty hat geschrieben:new und delete kapieren sie in zwei Minuten. Verschachtelte if(NULL == können sie in Python oder Java in Sekunden selber nachbauen; das musst du nicht einmal erklären.
„Da fehlt ein if(nullptr == …!“
deutlich häufiger zu hören sein als
„Da muss std::make_unique<T>() hin statt std::unique_ptr(new T())!“
bei der äquivalenten RAII-Version.
Mag ja sein, dass ifs weniger Quelltext sind. Aber in dem Augenblick, wo du mit RAII & Ausnahmen anfängst, machst du die hinteren 1500 Seiten mit dem Verhalten der 1000 Klassen in der Standardbibliothek zu einem Teil der Lösung. Und das lohnt nun mal erst, wenn du dadurch so viele Zeilen und so viel Zeit sparst, dass Auswendiglernen und regelmäßiges googlen, was denn dieses verdammte Template mit dem verdammten Lambda verdammtnochmal tut, aufgewogen wird. Aber das habe ich bis heute noch nicht gesehen.
Re: [C++] Try/Catch -> Objekt anlegen
Kennt sich jemand mit std::experimental::optional aus? Damit soll das wohl möglich sein.
Ohne Input kein Output.
Re: [C++] Try/Catch -> Objekt anlegen
Naja, ich muss Krishty zumindest zum Teil recht geben: Viele Konstrukte in C++ machen nicht wirklich von Anfang an Sinn.
Die Move-Semantics zum Beispiel. Sie sind gut, weil sie für die Standard-Container schon da sind, aber selbst schreiben würde ich Move-Constructors o.ä. erstmal nicht, weil es den Code echt kompliziert macht.
Aber: Wenn ich merke, dass ich Performance-Probleme habe o.ä., dann kann ich mir den Code an der Stelle nochmal anschauen und mit moves etc. optimieren.
Zu den Exceptions: Wenn man Exceptions benutzt, muss man anders planen.
Der Autor in dem Artikel gibt das Beispiel mit dem Noification Icon, das z.B. im "schlecht"-Fall nicht geladen werden kann, aber dennoch auf "visible" gesetzt wird.
Das Problem hier ist aber ein anderes: Er versucht einfach, den Code statt mit ErrorCodes mit Exceptions zu schreiben, und das ist komplett der falsche Ansatz.
Aber es ist Quatsch, den Error an der stelle zu catchen.
Anderes Beispiel:
Hier ist es sowas von Wurst ob das NotificationIcon Visible ist oder nicht.
Und das ist auch der Sinn von Exceptions: die werden "nach oben" propagiert; ich muss sie nicht dauernd prüfen.
Ein komplexes Programm besteht meist aus mehreren Untermodulen, und ich kann bei einer Exception z.B. das Untermodul zerstören und neu initialisieren.
Und da habe ich dann den Vorteil von Exceptions.
Ich glaub das std::experimental::optional ist wie boost.optional; da muss man vor dem Zugriff prüfen, ob das Element da ist; ich glaube das ist eine gute Sache.
Die Move-Semantics zum Beispiel. Sie sind gut, weil sie für die Standard-Container schon da sind, aber selbst schreiben würde ich Move-Constructors o.ä. erstmal nicht, weil es den Code echt kompliziert macht.
Aber: Wenn ich merke, dass ich Performance-Probleme habe o.ä., dann kann ich mir den Code an der Stelle nochmal anschauen und mit moves etc. optimieren.
Zu den Exceptions: Wenn man Exceptions benutzt, muss man anders planen.
Der Autor in dem Artikel gibt das Beispiel mit dem Noification Icon, das z.B. im "schlecht"-Fall nicht geladen werden kann, aber dennoch auf "visible" gesetzt wird.
Das Problem hier ist aber ein anderes: Er versucht einfach, den Code statt mit ErrorCodes mit Exceptions zu schreiben, und das ist komplett der falsche Ansatz.
Code: Alles auswählen
//statt
auto error = funktion();
if(error != NULL) ....
//will er
try{
funktion();
}
catch(...){ error_behandlung(); }
Anderes Beispiel:
Code: Alles auswählen
try { init_gui(); }
catch(...)
{
destroy_already_isitialized_gui_elements();
start_console_only();
}
Und das ist auch der Sinn von Exceptions: die werden "nach oben" propagiert; ich muss sie nicht dauernd prüfen.
Ein komplexes Programm besteht meist aus mehreren Untermodulen, und ich kann bei einer Exception z.B. das Untermodul zerstören und neu initialisieren.
Und da habe ich dann den Vorteil von Exceptions.
Ich glaub das std::experimental::optional ist wie boost.optional; da muss man vor dem Zugriff prüfen, ob das Element da ist; ich glaube das ist eine gute Sache.
- CodingCat
- Establishment
- Beiträge: 1857
- Registriert: 02.03.2009, 21:25
- Wohnort: Student @ KIT
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Ich spiele hier mal kurz den Straßenreinigungsdienst:
Zu letzterem gibt dieser Stack-Overflow-Thread einen umfassenden Überblick über die potentielle Bedeutung verschiedener unique_ptr-Typisierungen. Wichtig ist in erster Linie der Standardfall, nämlich unique_ptr<InitData> by Value, um garantierte Besitzübernahme des übergebenen Objekts zu spezifizieren (Aufruf erzwingt dann Temporary oder Move, Objekt wird danach garantiert zerstört). Sollte Editor tatsächlich den Besitz über die gegebenen Daten übernehmen, wäre unique_ptr<InitData> by Value also der richtige Parametertyp. Da weder GameA noch GameB eine Besitzübernahme anstreben, sind die einzig sinnvollen Parametertypen hier ein einfacher Zeiger oder eine einfache Referenz auf InitData:
unique_ptr<InitData>&& hingegen trifft man in freier Wildbahn eigentlich nie an, da die Bedeutung eine unsichere Besitzübernahme ist - d.h. es kann passieren, dass das übergebene Objekt seinen alten Besitzer behält. Mir fällt gerade keine Situation ein, in der ich eine derartige Semantik schonmal gebraucht hätte.
Nun zurück zum eigentlichen Problem: Ich sehe, was du mit der einheitlichen Entgegennahme von unique_ptr erreichen willst, im Sinne einheitlicher Konstruktorschnittstellen und statischer Polymorphie. Diese würde man unter Berücksichtigung der gerade gemachten Bermerkungen einfach durch einheitliche Entgegennahme von unique_ptr by Value umsetzen. Aber meiner Ansicht nach sollte die externe Problematik von Ressourcenverwaltung bei der Initialisierung überhaupt nicht mit den Klassen/Konstruktoren vermischt werden: Wenn ich ein GameA-Objekt anlege, sollte die Schnittstelle mich auf keinen Fall dazu zwingen, meine gesamten Initialisierungsdaten zu zerstören, nur um im konkreten Anwendungsfall eine einheitliche Schreibweise für unterschiedliches (statisch polymorphes) Verhalten zu haben.
Ich selbst trete in meinen eigenen Programmen nur selten den Besitz von Daten ab, viel öfter setze ich voraus, dass sich der Erzeuger von Objekten darüber im Klaren ist, welche langfristigen Abhängigkeiten über die Konstruktionszeit hinaus bestehen. (Ich selbst nutze die Unterscheidung von Referenzen und Zeigern dazu, in meinen Konstruktionsschnittstellen kurzfristige Abhängigkeiten (zur Konstruktionszeit) und langfristige Abhängigkeiten (darüber hinaus) eindeutig als solche zu krennzeichnen, in dieser Reihenfolge.) Meine Schnittstelle sähe also wie folgt aus:
Ich bin mir darüber im Klaren, dass das auch keine optimale Lösung ist, in dem Sinne dass sie die Lebenszeit von init_data im Editor-Fall nicht forciert. Diesen Kompromiss nehme ich in Kauf, um auf der Aufruferseite niemals zu Besitzabtritt gezwungen zu sein, d.h. es ist die Lösung, die "am wenigsten im Weg ist".
TL;DR: Kürzer gehts nicht, dafür lassen sich die verschiedenen Optionen jetzt hoffentlich besser bewerten.
Das halte ich für keine gute Idee. Zum einen hat die diskutierte Problematik mit keiner dieser Klassen und/oder Konstruktoren zu tun, zum anderen ergibt die konkret vorgeschlagene unique_ptr-Typisierung keinen Sinn.dot hat geschrieben:Besser wär es wohl, wenn die InitData per move an das jeweilige Objekt übertragen wird. Dieses kann dann entscheiden, ob die Daten weiter benötigt werden, oder ob sie freigegeben werden können:
Code: Alles auswählen
class GameA { ... GameA(std::unique_ptr<InitData> init_data) { ... } }; class GameB { ... GameB(std::unique_ptr<InitData> init_data) { ... } }; class Editor { ... Editor(std::unique_ptr<InitData>&& init_data) { ... } // she's a keeper ... };
Zu letzterem gibt dieser Stack-Overflow-Thread einen umfassenden Überblick über die potentielle Bedeutung verschiedener unique_ptr-Typisierungen. Wichtig ist in erster Linie der Standardfall, nämlich unique_ptr<InitData> by Value, um garantierte Besitzübernahme des übergebenen Objekts zu spezifizieren (Aufruf erzwingt dann Temporary oder Move, Objekt wird danach garantiert zerstört). Sollte Editor tatsächlich den Besitz über die gegebenen Daten übernehmen, wäre unique_ptr<InitData> by Value also der richtige Parametertyp. Da weder GameA noch GameB eine Besitzübernahme anstreben, sind die einzig sinnvollen Parametertypen hier ein einfacher Zeiger oder eine einfache Referenz auf InitData:
Code: Alles auswählen
class GameA {
...
GameA(InitData* init_data) { ... } // or InitData&
};
class GameB {
...
GameB(InitData *init_data) { ... } // or InitData&
};
class Editor {
...
Editor(std::unique_ptr<InitData> init_data) { ... } // she's a keeper ...
};
Nun zurück zum eigentlichen Problem: Ich sehe, was du mit der einheitlichen Entgegennahme von unique_ptr erreichen willst, im Sinne einheitlicher Konstruktorschnittstellen und statischer Polymorphie. Diese würde man unter Berücksichtigung der gerade gemachten Bermerkungen einfach durch einheitliche Entgegennahme von unique_ptr by Value umsetzen. Aber meiner Ansicht nach sollte die externe Problematik von Ressourcenverwaltung bei der Initialisierung überhaupt nicht mit den Klassen/Konstruktoren vermischt werden: Wenn ich ein GameA-Objekt anlege, sollte die Schnittstelle mich auf keinen Fall dazu zwingen, meine gesamten Initialisierungsdaten zu zerstören, nur um im konkreten Anwendungsfall eine einheitliche Schreibweise für unterschiedliches (statisch polymorphes) Verhalten zu haben.
Ich selbst trete in meinen eigenen Programmen nur selten den Besitz von Daten ab, viel öfter setze ich voraus, dass sich der Erzeuger von Objekten darüber im Klaren ist, welche langfristigen Abhängigkeiten über die Konstruktionszeit hinaus bestehen. (Ich selbst nutze die Unterscheidung von Referenzen und Zeigern dazu, in meinen Konstruktionsschnittstellen kurzfristige Abhängigkeiten (zur Konstruktionszeit) und langfristige Abhängigkeiten (darüber hinaus) eindeutig als solche zu krennzeichnen, in dieser Reihenfolge.) Meine Schnittstelle sähe also wie folgt aus:
Code: Alles auswählen
class GameA {
...
GameA(InitData& init_data) { ... } // construction-time dependency on init_data
};
class GameB {
...
GameB(InitData& init_data) { ... } // construction-time dependency on init_data
};
class Editor {
...
Editor(InitData* init_data) { ... } // run-time dependency on init_data
};
TL;DR: Kürzer gehts nicht, dafür lassen sich die verschiedenen Optionen jetzt hoffentlich besser bewerten.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
- dot
- Establishment
- Beiträge: 1746
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: [C++] Try/Catch -> Objekt anlegen
Stimmt, unique_ptr&& ist natürlich völliger Bullshit, :oops: ; als ich das geschrieben hab, muss ich wohl temporär etwas verwirrt gewesen sein (ich meinte natürlich, dass im Konstruktor die Daten in einen Member weitergemoved werden; aus irgendeinem Grund hat mein Hirn das dann zu unique_ptr&& übersetzt)...zumindest zeigt es, dass ich sowas niemals freiwillig anstellen würde... :PCodingCat hat geschrieben:Zum einen hat die diskutierte Problematik mit keiner dieser Klassen und/oder Konstruktoren zu tun, zum anderen ergibt die konkret vorgeschlagene unique_ptr-Typisierung keinen Sinn.
Yep, genau das war mein Hintergedanke. Die "korrekte" Lösung wäre natürlich, durchgehend allen einen unique_ptr by value zu geben, wie du oben sagst.CodingCat hat geschrieben:Nun zurück zum eigentlichen Problem: Ich sehe, was du mit der einheitlichen Entgegennahme von unique_ptr erreichen willst, im Sinne einheitlicher Konstruktorschnittstellen und statischer Polymorphie.
Stimme ich dir absolut zu, ich habe hier halt versucht, einen Weg zu finden, die gewünschte Entkopplung von Scope und Lebensdauer herzustellen. Wie gesagt, würde ich eher meine Motive in Frage stellen, bevor ich tatsächlich solchen Code schreiben würde. Insbesondere, wenn man bedenkt, dass der einzige Grund, wieso im Editor Fall die Daten weiterexistieren sollen, wohl sein wird, dass der Editor diese modifizieren will (!? ein weiterer Punkt, bei dem mit etwas mulmig wird)...CodingCat hat geschrieben:Aber meiner Ansicht nach sollte die externe Problematik von Ressourcenverwaltung bei der Initialisierung überhaupt nicht mit den Klassen/Konstruktoren vermischt werden: Wenn ich ein GameA-Objekt anlege, sollte die Schnittstelle mich auf keinen Fall dazu zwingen, meine gesamten Initialisierungsdaten zu zerstören, nur um im konkreten Anwendungsfall eine einheitliche Schreibweise für unterschiedliches (statisch polymorphes) Verhalten zu haben.
Seh ich absolut genau so, daher auch mein initialer Vorschlag mit unique_ptr und explizitem .reset() nach Inisialisierung.CodingCat hat geschrieben:Ich selbst trete in meinen eigenen Programmen nur selten den Besitz von Daten ab, viel öfter setze ich voraus, dass sich der Erzeuger von Objekten darüber im Klaren ist, welche langfristigen Abhängigkeiten über die Konstruktionszeit hinaus bestehen.
Es ist vermutlich auch ein guter Kompromiss, wenn es wirklich so sein muss. Ich würde unbedingt vorher nochmal drüber nachdenken, ob man den Teil der InitData, der bestimmt, welches Objekt konstruiert werden soll und den Rest nicht irgendwie separieren und das Problem somit aus der Welt schaffen kann. Rein intuitiv fühle ich mich in einer Situation mit derartiger Dissonanz zwischen Lebensdauer und Scope jedenfalls nicht wirklich wohl...CodingCat hat geschrieben:Ich bin mir darüber im Klaren, dass das auch keine optimale Lösung ist, in dem Sinne dass sie die Lebenszeit von init_data im Editor-Fall nicht forciert. Diesen Kompromiss nehme ich in Kauf, um auf der Aufruferseite niemals zu Besitzabtritt gezwungen zu sein, d.h. es ist die Lösung, die "am wenigsten im Weg ist".
Re: [C++] Try/Catch -> Objekt anlegen
Code: Alles auswählen
optional<Game> game;
try
{
game.emplace(ctor_args_for_Game);
}
catch (WhateverYouWant)
{
/* game is still in scope here, but you can detect that it doesn't hold a value */
}
/* game is still in scope here, but you can detect that it doesn't hold a value */
The value is guaranteed to be allocated within the optional object itself, i.e. no dynamic memory allocation ever takes place. Thus, an optional object models an object, not a pointer, even though the operator*() and operator->() are defined.
Leider wohl nicht mehr in C++14 enthalten.
Ohne Input kein Output.