66

Jeder Programmierer sollten wissen:De Morgan Gesetz Optimierung mit überladenen Operatoren

De Morgan 1
De Morgan 2
(De Morgan's Laws)

Unter bestimmten Umständen, um das Programm zu optimieren, kann es vorkommen, dass Compiler modifiziert (!p && !q)

Die beiden Ausdrücke sind gleichwertig, und es macht keinen Unterschied, ob die erste oder die erste Bewertung ausgewertet wird der Zweite.
Aber in C++ ist es möglich, Operatoren zu überlasten, und der überladene Operator kann diese Eigenschaft nicht immer respektieren. Wenn Sie also den Code auf diese Weise transformieren, wird der Code tatsächlich geändert.

Sollte der Compiler De Morgans Gesetze verwenden, wenn !, || und && überladen sind?

+11

Jeder vernünftiger Compiler Schriftsteller zu vertrauen vermeidet, dass der Programmierer korrekt den inversen Operator implementiert hat. Dies ist ein sehr häufiger Fehler. –

+6

Im Allgemeinen kann der Compiler solche Transformationen nur dann auf Ihr Programm anwenden, wenn sie das beobachtbare Verhalten (Nebeneffekte, Ausgabe) Ihres Programms nicht verändern. Wenn 'p' und' q' boolesche Primitive sind, können De Morgans Gesetze angewendet werden, da das das beobachtbare Verhalten nicht ändert. Wenn "p" und "q" überladene Operatoren haben, kann dies wahr sein oder auch nicht. Der C++ - Standard sagt nichts über De Morgans Gesetze aus; Compiler dürfen nur "davon Gebrauch machen", weil sie wissen, dass sie ihr Verhalten nicht ändern werden. – davmac

+0

natürlich nicht .... –

Antwort

76

Beachten Sie, dass:

Builtin Betreiber & & und || Kurzschlussauswertung durchführen (den zweiten Operanden nicht auswerten, wenn das Ergebnis nach Auswertung der ersten bekannt ist), aber überladene Operatoren verhalten sich wie normale Funktionsaufrufe und werten immer beide Operanden aus.

... Weil die Kurzschlusseigenschaften des Bedieners & & und des Bedieners || nicht auf Überladungen anwenden, und weil Typen mit boolescher Semantik ungewöhnlich sind, überlasten nur zwei Standardbibliotheksklassen diese Operatoren ...

Quelle: http://en.cppreference.com/w/cpp/language/operator_logical (Hervorhebung von mir)

Und das:

Wenn ein Benutzer geschriebenen Kandidaten mit dem gleichen Namen Bewerber und Parametertypen als Einbau in sind Funktion, die integrierte Operatorfunktion ist ausgeblendet und ist nicht in der Menge der Kandidatenfunktionen enthalten.

Quelle: n4431 13.6 Integrierte Operatoren [over.built] (Hervorhebung von mir)

Fassen wir zusammen: überladene Operatoren verhalten sich wie normale, vom Benutzer geschriebenen Funktionen.

NEIN, der Compiler ersetzt einen Aufruf einer benutzerdefinierten Funktion nicht durch einen Aufruf einer anderen benutzerdefinierten Funktion. Andernfalls würde möglicherweise die "as if" Regel verletzen.

17

Ich denke, dass Sie Ihre eigene Frage beantwortet haben: nein, ein Compiler kann dies nicht tun. Nicht nur die Operatoren können überlastet werden, manche können nicht einmal definiert werden. Zum Beispiel können Sie operator && und operator ! definiert haben und operator || nicht definiert werden.

Beachten Sie, dass es viele andere Gesetze gibt, denen der Compiler nicht folgen kann. Zum Beispiel kann es p||q zu q||p, sowie x+y zu y+x nicht ändern.

(Alle oben genannten gilt für überladene Operatoren, wie das ist, was die Frage verlangt.)

+1

Wenn Sie davon sprechen, nicht überlastet zu sein, kann der Compiler p || q in q || p ändern, wenn er beweist, dass q keine Nebeneffekte oder undefiniertes Verhalten hat, wenn p falsch ist, und p keine Nebenwirkungen hat, wenn q falsch ist . Also wenn (x> = 0 && x <= 100) kann vertauscht werden, aber wenn (p! = NULL && * p == 1) nicht kann. – gnasher729

+0

@ gnasher729, aktualisierte die Antwort, um zu zeigen, dass es sich um überladene Operatoren handelt, da dies die Frage ist, nach der gefragt wird. – Petr

+3

@ gnasher729 worüber Sie sprechen, ist das Ergebnis der Toleranz in der Spezifikation, dass Implementierungen "nur das beobachtbare Verhalten emulieren" müssen. Sicher, es kann 'q || neu anordnen p ', wenn es beweisen kann, dass das Ergebnis (einschließlich Nebenwirkungen) genau gleichwertig ist, aber da das Ergebnis _ist_ genau dann äquivalent ist, hat diese Neuanordnung keinen beobachtbaren Effekt. Die Frage, die OP stellt, ist, denke ich, speziell nach einer Neuanordnung zu fragen, die das beobachtbare Verhalten ändert. – davmac

9

Nein, in diesem Fall die Umwandlung ungültig wäre. Die Berechtigung zum Transformieren von !p && !q in !(p || q) ist implizit durch die Als-ob-Regel. Die Als-ob-Regel erlaubt jegliche Transformation, die grob gesagt von einem korrekten Programm nicht beobachtet werden kann. Wenn überladene Operatoren verwendet werden und die Transformation erkennen, bedeutet dies automatisch, dass die Transformation nicht länger zulässig ist.

+2

Es sei denn, der Compiler hat die überladenen Operatoren analysiert und festgestellt, dass die as-if-Regel weiterhin gilt. Unwahrscheinlich, dass ein Compiler sogar versucht :-) – gnasher729

+0

@ gnasher729: Der Compiler wird die überladenen Operatoren analysieren, aber nicht für den Zweck dieser Transformation. Nach dem Inlining könnte es vorkommen, dass es Möglichkeiten gibt, Operationen mit integrierten Typen zu transformieren, und redundantes Codefalten könnte dann dazu führen, dass es den im komplementären Operator implementierten Zweig wiederverwenden kann .... aber nichts davon ist ein expliziter Versuch, es anzuwenden DeMorgans Theorem für benutzerdefinierte Operatoren. –

+0

@BenVoigt Eigentlich, als ich diesen Kommentar gelesen habe, dachte ich, dass es eine interessante Heuristik zu sein scheint, die ein zukünftiger Compiler rechtmäßig versuchen könnte, selbst wenn die aktuellen Compiler dies nicht tun: sehen, ob Optimierungsmöglichkeiten entstehen, wenn solche Transformationen durchgeführt werden Überprüfen Sie, ob die Operatoren so implementiert sind, dass diese Transformation möglich ist. Für einige sehr spezielle Programme könnte es sehr nützlich sein, und wenn es die Gesamtleistung des Compilers nicht merklich beeinträchtigt, dann könnte das Grund genug sein, es so zu halten. (Ich denke es ist auch unwahrscheinlich, aber ich mag es trotzdem.) – hvd

5

Überladene Operatoren per se sind nur syntaktischer Zucker für Funktionsaufrufe; Der Compiler selbst darf keine Annahmen über die Eigenschaften treffen, die für solche Aufrufe gelten können oder nicht. Optimierungen, die die Eigenschaften eines bestimmten Operators ausnutzen (z. B. De Morgans für boolesche Operatoren, Kommutativität für Summen, Verteilbarkeit für Summe/Produkt, Transformation einer ganzzahligen Division in einer geeigneten Multiplikation, ...) können nur verwendet werden, wenn die "echten Operatoren" werden verwendet.

Hinweis statt, dass einige Teile der Standardbibliothek kann einige spezifische semantische Bedeutung zu überladenen Operatoren verknüpfen - zum Beispiel std::sort standardmäßig erwartet ein operator<, die zwischen den Elementen einer strengen schwache Ordnung entspricht - aber das ist natürlich aufgeführt in den Voraussetzungen jedes Algorithmus/Containers.

(übrigens Überlastung && und || wahrscheinlich ohnehin vermieden werden sollten, da sie ihre Kurzschließen Eigenschaften verlieren, wenn sie überlastet, so dass ihr Verhalten überraschend und damit potentiell gefährlich wird)

4

Sie fragen, ob der Compiler kann beliebig neu schreiben dein Programm etwas zu tun, das du nicht geschrieben hast.

Die Antwort ist: natürlich nicht!

  • Wo De Morgans Gesetze gelten, können sie angewendet werden.
  • Wo sie nicht tun, dürfen sie nicht.

Es ist wirklich so einfach.

+1

"Sie fragen, ob der Compiler Ihr Programm willkürlich umschreiben kann, um etwas zu tun, das Sie nicht geschrieben haben" - nein, OP fragt, ob der Compiler anwenden kann eine spezifische Transformation in bestimmten Fällen. – davmac

+1

@davmac Richtig, und diese spezifische Umwandlung wäre ein willkürliches Umschreiben des Programms, um etwas zu tun, das das OP nicht geschrieben hat, um es zu tun. –

+2

Nein, es wäre eine _spezielle_ (nicht willkürliche) Transformation ("rewriting") des Programms in einem bestimmten Fall. OP fragt, ob die Sprache dem Compiler erlaubt, diese Transformation durchzuführen. Das unterscheidet sich von der Frage, ob der Compiler beliebige Änderungen am Programm vornehmen kann. Es ist auch etwas "das OP hat es nicht geschrieben zu tun" hängt von den tatsächlichen Regeln der Sprache ab. Der C++ - Standard hat möglicherweise eine solche Umwandlung erlaubt; wie es passiert, ist es nicht - was das OP fragt. Die Antwort ist nur dann offensichtlich, wenn Sie es bereits wissen. – davmac

3

Nicht direkt.

Wenn p und q Ausdrücke sind, so dass p keine überladenen Operatoren hat, ist eine Kurzschlussevaluation wirksam: Ausdruck q wird nur ausgewertet, wenn p falsch ist.

Wenn p von nicht-primitiver Art ist, gibt es keine Kurzschlussauswertung und überladene Funktion könnte alles sein - auch nicht im Zusammenhang mit der herkömmlichen Verwendung.

Compiler wird seine Optimierungen auf seine eigene Art und Weise tun. Vielleicht könnte es zu de Morgans Identitäten kommen, aber nicht auf der Ebene der Ersatzlieferung.

3

DeMorgans Gesetze gelten für die Semantik dieser Betreiber. Überlastung gilt für die Syntax dieser Operatoren. Es gibt keine Garantie dafür, dass ein überladener Operator die Semantik implementiert, die für die Anwendung der DeMorgan-Gesetze erforderlich ist.

+1

"Es gibt keine Garantie, dass ein überladener Operator die Semantik implementiert, die für die Anwendung der DeMorgan-Gesetze erforderlich ist" - bestätigt OP dies in der Frage. Ich denke, du verstehst nicht, was OP zu fragen versucht. – davmac

+0

@davmac - Ich glaube, du hast meine Antwort falsch verstanden. –

+0

Ich denke, es ist möglich - vorsichtig zu erarbeiten? OP erkennt ziemlich klar, dass überladene Operatoren möglicherweise nicht die Semantik implementieren, die für die Anwendung der DeMorgan-Identitäten erforderlich ist. Ich glaube, dass OP fragt, ob der Compiler Ausdrücke nach diesen Identitäten unabhängig von Überladung transformieren darf und ob die Identität tatsächlich gilt (was zugegebenermaßen eine Interpretation ist, nicht die wörtliche Bedeutung der Frage, wie sie gestellt wird). Ihre Antwort scheint das nicht zu berücksichtigen. Sprechen Sie gerade die wörtliche Frage an? – davmac

1

Aber in C++ ist es möglich, Operatoren zu überlasten, und der überladene Operator kann diese Eigenschaft nicht immer respektieren.

Überladener Operator ist nicht länger ein Operator, es ist ein Funktionsaufruf.

class Boolean 
{ 
    bool value; 

    .. 

    Boolean operator||(const Boolean& b) 
    { 
     Boolean c; 
     c.value = this->value || b.value; 
     return c; 
    } 

    Boolean logical_or(const Boolean& b) 
    { 
     Boolean c; 
     c.value = this->value || b.value; 
     return c; 
    } 
} 

Also diese Zeile Code

Boolean a (true); 
Boolean b (false); 

Boolean c = a || b; 

entspricht diesen

Boolean c = a.logical_or(b);