Unterschied zwischen leerem Destruktor und default Desktruktor
-
Ich glaube, Du verwechselst hier etwas, aber es ist schwer das zu erkennen, weil Du dich so unglaublich vage ausdrückst, und außerdem glaube ich, dass Du meine Aussage irgendwie allgemeiner interpretierst, als ich sie artikuliert habe—es geht mir hier erst einmal nur um den lexikalisch leeren Destruktor. Soweit ich raten kann, beziehst Du dich auf die streng getrennten Phasen der Übersetzung in denen einmal ein AST mit der ganzen C++-spezifischen Information, und andererseits ein simpler IR Code (LLVM oder RTL) auf dem semantische Flussanalysen ausgeführt werden können, gegeben ist, wobei wir keine Abbildung zwischen beiden zur Verfügung haben. Ich stimme zu, dass wir i.A. nicht hoffen können, dass ein Compiler bestimmen kann, dass eine Funktion dieses oder jenes Verhalten hat, und diese Information wiederum der höheren Phase anbietet, um Typisierung zu lösen. Das würde schließlich mehrfache Iteration beider Phasen zur Folge haben, aber noch viel wichtiger, es würde Typisierung von semantischen Qualitäten abhängig machen, was gerade dringlichst zu vermeiden ist.
Hier geht es aber nicht um Semantik, sondern Syntax. Das Trait kann implementiert werden, indem wir eine simple, grammatische Untermenge von C++ erfassen, die keine semantischen Effekte hat. Das Endresultat wäre syntax & type driven.
Es wäre sogar möglich, im Standard selbst vorzugeben, dass leere Destruktoren die lexikalisch innerhalb der Klasse liegen, besonders berücksichtigt werden, und triviale Zerstörbarkeit nicht ausschließen, nur um dir mal zu vergegenwärtigen, dass mein konkretes Beispiel einfacher ist, als Du es ausmachst... nicht, dass solche Dinge etwas in den Sprachregeln verloren hätten!
Es gibt ein paar Traits, die ein bisschen in die Richtung Inspektion gehen, wie die is_nothrow_Xable. Wie du aber merken wirst, beziehen diese sich bloß auf die explizite Markierung der untersuchten Objekte mittels
noexcept
ähnlichem. Also Versprechen, die der Programmierer an dieser Stelle machen kann, damit Optimierungen frühzeitig durchgeführt werden können, wo es Sinn macht, und der Compiler kann dann bei einem späteren Durchgang prüfen, ob das Versprechen auch gehalten wurde. Aber umgekehrt kann der Compiler nicht frühzeitig sagen, ob etwas noexcept ist, außer der Programmierer gibt ihm dieses Versprechen. Das =default geht in die gleiche Richtung und tatsächlich gibt es ja auch jede Menge Traits, die sich darauf beziehen.Das klingt nicht sonderlich überzeugend; siehe bspw. die Diskussionen/Paper bzgl. Deduktion von
constexpr(auto)
undnoexcept(auto)
. Hier muss der Compiler in einer höheren Phase den Inhalt einer Funktion inspizieren und ein entsprechendes Attribut an diese Funktion anheften. Ich bin aber auch unsicher, was Du mit "Codeinspektion" meinst, also semantische oder syntaktische Inspektion.
-
@seppj sagte in Unterschied zwischen leerem Destruktor und default Desktruktor:
@tggc sagte in Unterschied zwischen leerem Destruktor und default Desktruktor:
Ja, aber wuerde es nicht reichen ein bool has_empty_destructor()<T> einzufuehren um das Problem zu loesen, oder nicht?
Wie soll das funktionieren? Soll das Template den Code inspizieren?
Im simpelsten Fall Schreibt man sich ein Macro, was einen leeren Konstruktor und das gleichzeitig definiert.
Das Buch sagt doch ersetze alle ~T(){} mit ~T() = default. Und diese simple Anweisung ist offensichtlich auch maschinell ausführbar, wenn du meinst das wäre nicht so das musst du schon mit einem Argument kommen und nicht nur "ja das ist eben unendlich schwer.". Und wie gesagt ist selbst das für mich kein Argument, denn grundsätzlich bedeutet das auch erstmal nichts.
Vorhin meintest du ja auch erst:
@seppj sagte in Unterschied zwischen leerem Destruktor und default Desktruktor:Viel Spaß beim Beweis, ob die Schleife überhaupt hält...
Na und das geht ja offensichtlich.@seppj sagte in Unterschied zwischen leerem Destruktor und default Desktruktor:
Es ist auch ungleich einfacher zu erkennen, wie oft eine Schleife läuft, als zu erkennen, was der Unterschied des Programmzustands zwischen Beginn und Abschluss einer Schleife ist.
Aber das war auch gar nicht die Aufgabe. Was wenn nur eine ganz bestimmte Schleife erkannt werden, z.b. eine die ohnehin unrolled wird? Das unrollen beweist ja schon, das es erkennbar ist. Oder eine simple peephole Optimization, welche zwei mov hintereinander ersetzt wenn die Adresse aufeinanderfolgen, diese Regel kann dann wiederholt angewendet werden. So fundamental ist dieses Problem nicht, wie du es machen willst. Eher so uninteressant, das es keinen interessiert.
Und umgekehrt würde auch hier ein minimale Anpassung reichen:
http://en.cppreference.com/w/cpp/concept/TriviallyCopyableWir bräuchten hier nur sagen das ein trivial destructor auch ein leerer inline Destruktor sein kann fuer TriviallyCopyable2 und das dann an entsprechenden Stellen benutzen. Und auch hier höre ich gern deine Begründung warum " ~T() = default" das ausloesen kann, "~T(){}" aber nicht.
-
@tggc sagte in Unterschied zwischen leerem Destruktor und default Desktruktor:
Das Buch sagt doch ersetze alle ~T(){} mit ~T() = default. Und diese simple Anweisung ist offensichtlich auch maschinell ausführbar, wenn du meinst das wäre nicht so das musst du schon mit einem Argument kommen und nicht nur "ja das ist eben unendlich schwer.". Und wie gesagt ist selbst das für mich kein Argument, denn grundsätzlich bedeutet das auch erstmal nichts.
Bist du da so sicher? Als Beweis, dass ich hier keine obskuren Beispiele heranziehe, an denen ich lange getüftelt habe¹, nehmen wir doch einfach mal das Beispiel aus dem Buch. Das ist simpel. Keine Tricks, keine Überladungen, Templatemagie, keine Sichtbarkeitsklugscheißereien, keine virtuellen Funktionen, keine Vererbung. einfach nur ein struct mit zwei int Membern. Einfacher geht es nicht. Das sollte doch ohne Probleme funktionieren, oder?
#include <iostream> struct Point { int x,y; ~Point(){} }; int main() { Point p; std::cout << p.x << ' ' << p.y << '\n'; // Ein bisschen Entropie, um meinen Zufallsgenerator zu initialisieren. }
Jetzt ersetzen wir einfach den Destruktor durch den default.
#include <iostream> struct Point { int x,y; ~Point()=default; }; int main() { Point p; std::cout << p.x << ' ' << p.y << '\n'; // Hoppla, alles 0? }
Ups.
Wie ich schon sagte: Ihr beiden seid eindeutig die einzigen beiden klugen Menschen auf der Welt. Ihr habt an einem Nachmittag gelöst, woran andere seit Jahrzehnten verzweifeln.Und das obwohl ihr euch schwer tut mit der Erklärung, wieso andere ein Problem damit haben. Die anderen sind schlieslich alle dumm und ihr habt als einzige die Lücken in der Erklärung erkannt.
¹: Wer weiß, was in der Sprache des most vexing parse so alles möglich ist? Bist du wirklich in der Lage, zu beweisen, dass dein Vorschlag mit allen der vielen möglichen Schweinereien funktioniert?
-
@seppj sagte in Unterschied zwischen leerem Destruktor und default Desktruktor:
Wie ich schon sagte: Ihr beiden seid eindeutig die einzigen beiden klugen Menschen auf der Welt. Ihr habt an einem Nachmittag gelöst, woran andere seit Jahrzehnten verzweifeln.Und das obwohl ihr euch schwer tut mit der Erklärung, wieso andere ein Problem damit haben. Die anderen sind schlieslich alle dumm und ihr habt als einzige die Lücken in der Erklärung erkannt.
Aber am Ende bist schlieslich Du derjenige, der klug ist.
Und dein erster Code ist undefiniert.
Und Du hast dich nicht einmal auf meine letzte Antwort bezogen.
-
Warum erklärst du nicht, was der Unterschied ist, ich habe doch schon mehrfach danach gefragt? Oder weisst du es nicht? Nach der Logik ist die Aussage des Buches ja auch falsch, da man die Varianten nicht einfach austauschen darf. Also nochmal, warum ist ein POD trivial kopierbar, aber die Point struct nicht?
Dein Beispiel ist aus meiner Sicht Unsinn, weil da ja ehh was beliebiges passieren darf, also auch eine beliebige Optimierung angewandt werden darf. Dieser Grundsatz dürfte dir ja bekannt sein.
-
@tggc sagte in Unterschied zwischen leerem Destruktor und default Desktruktor:
Warum erklärst du nicht, was der Unterschied ist, ich habe doch schon mehrfach danach gefragt? Oder weisst du es nicht? Nach der Logik ist die Aussage des Buches ja auch falsch, da man die Varianten nicht einfach austauschen darf. Also nochmal, warum ist ein POD trivial kopierbar, aber die Point struct nicht?
Weil das Konzept trivialer Kopierbarkeit—welches u.a. auch als Trait im Typsystem eine Rolle spielt—nicht von dem Inhalt einer Funktion abhängen sollte, selbst wenn man diesen (wie ich oben erwähnt habe) grammatisch beschränken kann. Willst Du als nächstes
operator=
als trivial markieren, wenn dieser einmemcpy
durchführt? SeppJ hat schon Recht, dass der allgemeine Fall nicht realistisch entscheidbar ist, und daher brauchen wir die ganze Abteilung "Funktionskörper" gar nicht anfassen.
-
Ich habe es jetzt 2x ausführlich erklärt. Den Fakt, dass idie Optimierung nicht gemacht wird, obwohl sie das beobachtbare Verhalten nicht ändern würde, könnt ihr mit jedem Compiler nachvollziehen, egal ob ihr meine Erklärung nun versteht oder nicht. Was soll ich da noch mehr tun? Mehr Beispiele liefern, wieso es nicht so einfach ist? Ihr akzeptiert nicht einmal das denkbar einfachste Beispiel, das ich nicht einmal selbst schreiben musste, sondern einfach unverändert das Simpelbeispiel des TE genommen habe. Da ist doch ausgeschlossen, dass euch obskurere Beispiele helfen.
Ihr habt also:
- Objektiv prüfbare Fakten, dass die Vorgeschlagene Optimierung nicht gemacht wird
- Eine Erklärung dazu, wieso es technisch schwierig bis unmöglich ist, die vorgeschlagene Optimierung durchzuführen
- Gegenbeispiele zu eurer Gegenerklärung, wieso ihr denkt, dass das alles ganz einfach wäre
Und ihr sagt trotzdem "Nein, das glaube ich nicht". Was soll ich da noch weiter machen? Nein, beantwortet diese Frage nicht, ich sehe nämlich keinen Sinn, noch mehr zu liefern. Es ist alles gesagt, jede weitere Antwort wäre eine Wiederholung des bereits gesagtem mit anderen Worten. Das könnt ihr oben selber nachlesen.
-
@seppj Hä? Dein Beispiel oben ist unzulässig, weil es undefiniert ist. Da ist es völlig wurscht, welchen Effekt unsere Umschreibung hat.
Und Du hast dich wieder nicht auf meine Antwort bezogen. Zitiere mal einen Teil davon, und erkläre mir, wo ich etwas missverstanden habe. Und deine Erklärung ist sowas von absoluter bs:
Jetzt willst du ernsthaft einen Optimierer bauen, der for-Schleifen semantisch(!) analysiert und erkennt, dass die for-Schleife aus dem Beispiel durch ein memmove ersetzbar ist?
Willst du mich jetzt völlig veralbern oder was? Das ist schon längst gängige Praxis!
-
Also aus meiner Sicht ist es Wurst ob es schon gemacht wird oder nicht. Für mich ist "es wird nicht gemacht" und daraus folgt es ist sehr schwer oder auch unmöglich keine logische Begründung. Mit glauben hat das ja nun nichts zu tun. Würde mich daher freuen, wenn auf meine Anmerkung genauer eingegangen wird.
-
@seppj sagte in Unterschied zwischen leerem Destruktor und default Desktruktor:
Jetzt ersetzen wir einfach den Destruktor durch den default.
#include <iostream> struct Point { int x,y; ~Point()=default; }; int main() { Point p; std::cout << p.x << ' ' << p.y << '\n'; // Hoppla, alles 0? }
Ups.
Muss da nicht
Point p = Point();
oder sowas stehen damit man hier zero-init bekommt? War zumindest mal so...
-
@SeppJ: Ich würde auch gerne deine Definition von "benötigt Codeinspektion" wissen.
-
@hustbaer Das ist der springende Punkt. Es ist undefiniert, uninitialisierte
int
Variablen auszuwerten. Aber sobald der Code sie initialisiert, passiert das unabhängig davon, ob ein Destruktor definiert wurde oder nicht. Also macht das Beispiel einfach allgemein keinen Sinn.
-
Ich habe halt mit dem Beispiel des TE gearbeitet, um zu zeigen, dass selbst am denkbar einfachsten Beispiel eine einfache Pauschalersetzung zu einer Verhaltensänderung führt. Und schönerweise sogar auf eine Art und Weise, die Parallelen zu einem der größten Fehler der Softwaregeschichte aufweisen.
Hier, der nächste Verhaltensunterschied bei automatischer Pauschalersetzung, am wohl zweiteinfachsten denkbaren Beispiel:
class Foo { ~Foo(){} }; struct Bar: public Foo { ~Bar(){} };
class Foo { ~Foo()=default; }; struct Bar: public Foo { ~Bar()=default; };
Man muss halt schon wissen, was man da tut und den Code und sein Verhalten verstehen.
-
@c-olumbo Weisst du zufällig die Stelle wo definiert wird wann Member einer Klasse T, wenn sie mit T() instanziert wird (also z.B.
new T()
statt einfach nurnew T
) value-initialized werden und wann nicht?Ich weiss nur mehr: sobald die Klasse einen nicht-trivialen Ctor hat wird nix mehr von selbst value-initialized. Was schnell dadurch passieren kann dass man ein Member hinzufügt welches nen nicht trivialen Ctor hat. Die restlichen Bedingungen weiss ich aber nimmer... Ist der Dtor da wirklich egal?
-
@seppj Ich muss gestehen ich bin wohl zu doof für das Beispiel. Ich meine klar, im ersten Fall haben Foo und Bar beide einen nicht-trivialen Dtor und im zweiten Fall beide einen trivialen. Was offensichtlich ist, und daher denke ich mal dass es dir nicht darum geht...
-
@hustbaer sagte in Unterschied zwischen leerem Destruktor und default Desktruktor:
@seppj Ich muss gestehen ich bin wohl zu doof für das Beispiel. Ich meine klar, im ersten Fall haben Foo und Bar beide einen nicht-trivialen Dtor und im zweiten Fall beide einen trivialen. Was offensichtlich ist, und daher denke ich mal dass es dir nicht darum geht...
Das eine compiliert, das andere nicht.
-
Ja die Version mit "= default" kompiliert wobei das komisch ist.
In Foo sollte der Destructor eigentlich private sein. Scheinbar wird aber beim verwenden von "=default" der destructor public definiert.
Es könnte natürlich auch sein, dass in dem konkreten beispiel vom Compiler überhaupt kein destructor definiert wird, da die objekte keine member enhalten wofür ein destruktor notwendig wäre (soweit ich das verstehe)
-
@seppj Achje, ja, wegen dem privaten Dtor. Danke. Ist aber auch feig struct und class so zu mischen
-
@hustbaer Wenn der Initializer
()
ist, wird sie value-initialized.To value-initialize an object of type
T
means:
(8.1) ifT
is a (possibly cv-qualified) class type with either no default constructor ([class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
(8.2) ifT
is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and ifT
has a non-trivial default constructor, the object is default-initialized;
(8.3) ifT
is an array type, […]
(8.4) otherwise, the object is zero-initialized.Natürlich ist der Destruktor egal: Es geht hier um Initialisierung, und da zählen die Konstruktoren. Ein Aggregat ist ebenfalls völlig unabhängig von etwaigen Destruktoren definiert.
@hustbaer sagte in Unterschied zwischen leerem Destruktor und default Desktruktor:
Ich weiss nur mehr: sobald die Klasse einen nicht-trivialen Ctor hat wird nix mehr von selbst value-initialized. Was schnell dadurch passieren kann dass man ein Member hinzufügt welches nen nicht trivialen Ctor hat.
Oben können wir sehen, dass das nicht stimmt. Siehe auch hier. Wobei anzumerken wäre, dass bei GCC der erste
assert
fliegt. Könnte sich um einen Bug handeln?
-
Oh, ein private Destruktor verhält sich anders als ein public, wer hätte das gedacht.
Ich weiss echt nicht worauf du hinaus willst. Was hat das alles mit der (Un)möglichkeit zu tun eine gewisse Optimierung automatisch zu machen, die sonst nur durch eine simple Änderung des Code ermoeglicht wird.