[C++] Try/Catch -> Objekt anlegen

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

[C++] Try/Catch -> Objekt anlegen

Beitrag von BeRsErKeR »

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. :(
Ohne Input kein Output.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von dot »

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...
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von BeRsErKeR »

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. ;)
Ohne Input kein Output.
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von Helmut »

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.
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von Krishty »

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();
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von dot »

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.
Wenn du Objekte hast, die nur während der Konstruktion eines anderen Objektes existieren sollen, wieso werden die nicht lokal im Konstruktor erzeugt?

Oder aber auch einfach so:

Code: Alles auswählen

  Game game(InitData());
  game.run();
Im absoluten Notfall, muss man halt mit placement new in einen lokalen buffer ran...
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von Krishty »

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).
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von dot »

Krishty hat geschrieben:Ich weiß dummerweise erst nach Laden der Daten, welches von drei Spielen gestartet werden soll.
Und wo findet die Entscheidung statt? Mit Code wie dem obigen funktioniert das dann ja genauso nicht!?
Krishty hat geschrieben:Außerdem würde der Compiler dann sizeof(InitData) + sizeof(Game) an Stapelspeicher verbrauchen (was bei mir wirklich zum Problem wurde).
Na dann halt

Code: Alles auswählen

  Game game(std::make_unique<InitData>().get());
  game.run();
oder noch besser mit sowas wie einem scoped_ptr...
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von Krishty »

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.
dot hat geschrieben:Na dann halt

Code: Alles auswählen

  Game game(std::make_unique<InitData>().get());
  game.run();
oder noch besser mit sowas wie einem scoped_ptr...
Jetzt sind die Quelldateien immernoch allokiert wenn das Spiel startet.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von Krishty »

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();
  }
dot hat geschrieben:Na dann halt

Code: Alles auswählen

  Game game(std::make_unique<InitData>().get());
  game.run();
oder noch besser mit sowas wie einem scoped_ptr...
Jetzt sind die Quelldateien immernoch allokiert wenn das Spiel startet.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von dot »

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();
  }
So etwas zu bauen, gibt mir prinzipiell ein flaues Gefühl im Magen; aber die einfachste Lösung wäre wohl:

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();
  }
Auf jeden Fall ist mir weiterhin unklar, wo genau das Problem mit RAII und Exceptions liegen soll.
Krishty hat geschrieben:
dot hat geschrieben:Na dann halt

Code: Alles auswählen

  Game game(std::make_unique<InitData>().get());
  game.run();
oder noch besser mit sowas wie einem scoped_ptr...
Jetzt sind die Quelldateien immernoch allokiert wenn das Spiel startet.
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...
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von Krishty »

dot hat geschrieben:
Krishty hat geschrieben:Jetzt sind die Quelldateien immernoch allokiert wenn das Spiel startet.
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...
Stimmt; das war ein Brainfart :oops:
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();
  }
Jetzt sind die Quelldateien aber immernoch allokiert wenn das Spiel startet, oder? (Zumindest sagt dieses cppreference-Beispiel, dass man nach release() explizit deleten muss.)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von dot »

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.)
Sry, das stimmt natürlich, muss man natürlich reset() verwenden... :oops:
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von Krishty »

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von dot »

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.
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von Krishty »

seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von dot »

Krishty hat geschrieben:Sooo; und das ist das Problem mit RAII. Die Ressourcen werden nicht immer stapelmäßig aufgebaut, [...]
Und genau dort kommt move Semantik ins Spiel...

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 ... }
};
Krishty hat geschrieben:Und schwerer zu verstehen.
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...

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...
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von Krishty »

dot hat geschrieben:
Krishty hat geschrieben:Sooo; und das ist das Problem mit RAII. Die Ressourcen werden nicht immer stapelmäßig aufgebaut, [...]
Und genau dort kommt move Semantik ins Spiel...
Ja genau. Wir töten die Problematik durch noch mehr Komplexität! :)

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von dot »

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.
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:Es ist bloß kognitive Last, die nebenbei noch alles aufbläht damit die Leute sich schneller einen neuen Computer kaufen müssen.
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!?)...
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von Spiele Programmierer »

Exceptions sind kompliziert. Ohne Exception-Support der Sprache allerdings noch mehr.
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.
Move Semantic erhöht anscheinend nachweislich in einiger Software die Effizienz merklich.
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.
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von Krishty »

dot hat geschrieben:
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.
Wunderbar und dann können sie superschnell und effizient falschen Code schreiben, der gerade hinreichend richtig aussieht, dass es keinem auffallen muss...
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, wird
  „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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von BeRsErKeR »

Kennt sich jemand mit std::experimental::optional aus? Damit soll das wohl möglich sein.
Ohne Input kein Output.
NytroX
Establishment
Beiträge: 365
Registriert: 03.10.2003, 12:47

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von NytroX »

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.

Code: Alles auswählen

//statt
auto error = funktion();
if(error != NULL) ....

//will er
try{
  funktion();
}
catch(...){ error_behandlung(); }
Aber es ist Quatsch, den Error an der stelle zu catchen.

Anderes Beispiel:

Code: Alles auswählen

try { init_gui(); }
catch(...)
{
  destroy_already_isitialized_gui_elements();
  start_console_only();
}
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.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von CodingCat »

Ich spiele hier mal kurz den Straßenreinigungsdienst:
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 ...
};
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.

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 ...
};
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:

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
};
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.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von dot »

CodingCat 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.
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... :P
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.
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: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.
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: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.
Seh ich absolut genau so, daher auch mein initialer Vorschlag mit unique_ptr und explizitem .reset() nach Inisialisierung.
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".
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...
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: [C++] Try/Catch -> Objekt anlegen

Beitrag von BeRsErKeR »

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 */
Der Vorteil ist folgender:
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.
Antworten