it-swarm.com.de

Pre-Inkrement vs. Post-Inkrement

Im Google C++ Style Guide heißt es:

Vorinkrement und Vorkrementierung

Verwenden Sie das Präfixformular (++i) Der Inkrement- und Dekrementoperatoren mit Iteratoren und anderen Vorlagenobjekten.

Wenn eine Variable inkrementiert (++i Oder i++) Oder dekrementiert (--i Oder i--) Und der Wert des Ausdrucks nicht verwendet wird, muss entschieden werden ob vorinkrementieren (dekrementieren) oder nachinkrementieren (dekrementieren).

Wenn der Rückgabewert ignoriert wird, ist das "Pre" -Formular (++i) Nie weniger effizient als das "Post" -Formular (i++) Und häufig effizienter. Dies liegt daran, dass nach dem Inkrementieren (oder Dekrementieren) eine Kopie von i erstellt werden muss. Dies ist der Wert des Ausdrucks. Wenn i ein Iterator oder ein anderer nicht skalarer Typ ist, kann das Kopieren von i teuer sein. Da sich die beiden Inkrementtypen beim Ignorieren des Werts gleich verhalten, warum nicht einfach immer vorinkrementieren?

In C entwickelte sich die Tradition, Post-Inkrement zu verwenden, wenn der Ausdruckswert nicht verwendet wird, insbesondere in for-Schleifen. Einige finden Post-Inkrement leichter zu lesen, da das "Betreff" (i) dem "Verb" (++) Vorangeht, genau wie in Englisch.

Für einfache skalare (Nicht-Objekt-) Werte gibt es keinen Grund, eine Form zu bevorzugen, und wir erlauben dies auch. Verwenden Sie für Iteratoren und andere Vorlagentypen das Vorinkrement.

Ich muss wirklich nicht zustimmen mit "Wenn der Rückgabewert ignoriert wird, ist das" Pre "-Formular (++i) Nie weniger effizient als das" Post "-Formular (i++) und ist häufig effizienter. Dies liegt daran, dass nach dem Inkrementieren (oder Dekrementieren) eine Kopie von i erstellt werden muss. Dies ist der Wert des Ausdrucks. "

Zumindest wenn es um Zeiger geht (und Zeigerregister können als ganze Zahlen verwendet werden).

In meiner Hardware-Zeit gab es viele Maschinenanweisungen, die eine Form von Adressierungsmodi hatten, in die Nachinkrementierung, Nachdekrementierung und Vordekrementierung integriert waren. Die Post-Inc- und Post-Dec-Modi kosten 0 zusätzliche Anweisungen Zyklen mehr als gar kein Inkrement. Das Inkrementieren erfolgte, nachdem der Befehl ausgeführt wurde, während der nächste Opcode abgerufen wurde, aber die Vordekoration erforderte einen zusätzlichen Befehlszyklus, bevor das Register verwendet wurde. Also verstehe ich es nicht wirklich. Kann mir jemand den Fall von Google erklären?

Sie können in Bezug auf Zeiger Recht haben.

Jedoch:

  • C ist nicht C++. C++ garantiert eine sehr genaue Semantik, insbesondere beim Erstellen von Kopien, da dies RAII betrifft: In C++ sind Kopien ein beobachtbarer Effekt.

  • Nicht alle Iteratoren sind Zeiger, können jedoch komplexere Objekte sein. Geben Sie beispielsweise Iteratoren wie std::back_insert_iterator oder Iteratoren über nicht zusammenhängende Datenstrukturen wie std::unordered_map.

Ich muss wirklich nicht zustimmen mit "Wenn der Rückgabewert ignoriert wird, ist das" Pre "-Formular (++ i) nie weniger effizient als das" Post "-Formular (i ++) und oft effizienter. Dies liegt daran Nach dem Inkrementieren (oder Dekrementieren) muss eine Kopie von i erstellt werden. Dies ist der Wert des Ausdrucks. "

zumindest wenn es um Zeiger geht.

Hier gibt es nichts zu widersprechen. Das "++i ist nie weniger effizient als i++ ”Bedeutet nur, dass sie gleich effizient sein können (was Sie für Zeiger argumentieren). Aber wenn es gibt einen Unterschied, dann ++i wird voraussichtlich eine bessere Leistung erbringen.

Um genau zu sein, argumentieren Sie, dass das Nachdekrementieren effizienter bei bestimmten Architekturen sein kann, die ein effizienteres Nachdekrementieren als das Vordekrementieren garantieren. Diese Überlegungen gelten jedoch nur, wenn Sie für eine bestimmte Mikroarchitektur entwickeln. Für tragbaren Code sind die Garantien der Sprache selbst wichtiger. AFAIK weder x86 noch ARM Befehlssatzfamilien enthalten einen speziellen Befehl nach dem Inkrementieren.

Es ist natürlich möglich, ein Gegenbeispiel zu erstellen, in dem der Vorinkrementierungsoperator absichtlich unnötige Arbeit leistet, jedoch für eine konsistente und minimale Definition von Nachinkrementierung und Vorinkrementierung Inkrementieren, dann kann (und sollte) das Nachinkrementieren in Bezug auf das Vorinkrement implementiert werden:

struct X {
  ...
  // pre-increment
  X& operator++() { ...; return *this; }

  // post-increment
  X operator++(int) {
    X copy(*this);
    operator++();
    return copy;
  }
};

Da das Nachinkrementieren den Status des Objekts zurückgeben muss, bevor das Inkrement ausgeführt wurde, ist eine Kopie des Status vor dem Inkrementieren für benutzerdefinierte Typen unvermeidbar.

Natürlich kann ein ausreichend intelligenter Compiler das Post-Inkrement inline setzen und die Kopie vermeiden (unter der Annahme eines trivialen Kopier-Ctors), indem er die Sequenzierungsregeln und die Als-ob-Regel ausnutzt, aber bestenfalls sind wir wieder bei „gleich effizient“.

14
amon

Anstatt:

"Einige finden Post-Inkrement leichter zu lesen, da das 'Betreff' (i) dem 'Verb' (++) vorausgeht, genau wie in Englisch."

Versuchen Sie dies aus Gründen der Lesbarkeit und Effizienz:

Einige finden das Vorinkrement leichter zu lesen, da das "Objekt" (i) dem "Verb" (++) folgt, genau wie in Englisch. (Dies ist ein Beispiel für ein "verstandenes Thema".)

Lesen Sie "++ i" als "Inkrement i".