Wenn ich zwei verschiedene konstante Elementvariablen habe, die beide basierend auf demselben Funktionsaufruf initialisiert werden müssen, gibt es eine Möglichkeit, dies zu tun, ohne die Funktion zweimal aufzurufen?
Zum Beispiel eine Bruchklasse, bei der Zähler und Nenner konstant sind.
int gcd(int a, int b); // Greatest Common Divisor
class Fraction {
public:
// Lets say we want to initialize to a reduced fraction
Fraction(int a, int b) : numerator(a/gcd(a,b)), denominator(b/gcd(a,b))
{
}
private:
const int numerator, denominator;
};
Dies führt zu Zeitverschwendung, da die GCD-Funktion zweimal aufgerufen wird. Sie können auch ein neues Klassenmitglied definieren gcd_a_b
und zuerst die Ausgabe von gcd der Ausgabe in der Initialisierungsliste zuweisen. Dies würde jedoch zu einer Verschwendung von Speicher führen.
Gibt es im Allgemeinen eine Möglichkeit, dies ohne verschwendete Funktionsaufrufe oder Speicher zu tun? Können Sie vielleicht temporäre Variablen in einer Initialisierungsliste erstellen? Vielen Dank.
-O3
. Aber wahrscheinlich würde es für jede einfache Testimplementierung tatsächlich den Funktionsaufruf inline. Wenn Sie__attribute__((const))
den Prototyp verwenden oder rein verwenden, ohne eine sichtbare Definition anzugeben, sollte GCC oder Clang die Eliminierung gemeinsamer Subexpressionen (CSE) zwischen den beiden Aufrufen mit demselben Argument durchführen. Beachten Sie, dass Drews Antwort auch für nicht reine Funktionen funktioniert, daher ist es viel besser und Sie sollten sie immer dann verwenden, wenn die Funktion möglicherweise nicht inline ist.Antworten:
Ja. Dies kann mit einem delegierenden Konstruktor erfolgen , der in C ++ 11 eingeführt wurde.
Eine Delegation von Konstruktor ist eine sehr effiziente Art und Weise temporäre Werte für den Bau benötigt zu erwerben , bevor alle Membervariablen initialisiert werden.
quelle
.h
) sichtbar machen , auch wenn die eigentliche Konstruktordefinition für Inlining nicht sichtbar ist. Das heißt, dergcd()
Aufruf würde in jede Konstruktor-Aufrufseite einbinden und nur einencall
privaten Konstruktor mit drei Operanden überlassen .Die Mitgliedsvariablen werden in der Reihenfolge initialisiert, in der sie in der Klassendeklaration deklariert sind. Daher können Sie (mathematisch) Folgendes tun:
Sie müssen keine anderen Konstruktoren aufrufen oder erstellen.
quelle
Fraction(a,b,gcd(a,b))
Delegation in den Anrufer einbinden, was zu geringeren Gesamtkosten führt. Dieses Inlining ist für den Compiler einfacher, als die zusätzliche Unterteilung darin rückgängig zu machen. Ich habe es nicht auf godbolt.org versucht, aber du könntest es, wenn du neugierig bist. Verwenden Sie gcc oder clang-O3
wie bei einem normalen Build. (C ++ basiert auf der Annahme eines modernen Optimierungs-Compilers, daher Funktionen wieconstexpr
)@Drew Dormann gab eine ähnliche Lösung wie ich es mir vorgestellt hatte. Da OP niemals erwähnt, dass der ctor nicht geändert werden kann, kann dies aufgerufen werden mit
Fraction f {a, b, gcd(a, b)}
:Nur auf diese Weise gibt es keinen zweiten Aufruf einer Funktion, eines Konstruktors oder auf andere Weise, sodass keine Zeit verschwendet wird. Und es wird kein Speicher verschwendet, da ohnehin ein temporärer Speicher erstellt werden müsste. Sie können ihn also auch gut nutzen. Es vermeidet auch eine zusätzliche Aufteilung.
quelle
const
, aber zumindest für andere Typen. Und welche zusätzliche Teilung vermeiden Sie "auch"? Du meinst gegen Asmmos Antwort?,gcd(foo, bar)
ist zusätzlicher Code, der aus jeder Call-Site in der Quelle herausgerechnet werden könnte und sollte . Dies ist ein Problem der Wartbarkeit / Lesbarkeit, nicht der Leistung. Der Compiler wird es höchstwahrscheinlich zur Kompilierungszeit einbinden, was Sie für die Leistung wünschen.Fraction f( x+y, a+b );
Um es so zu schreibenBadFraction f( x+y, a+b, gcd(x+y, a+b) );
, müssten Sie tmp vars schreiben oder verwenden. Oder noch schlimmer: Was ist, wenn Sie schreiben möchtenFraction f( foo(x), bar(y) );
? Dann muss die Aufrufsite einige tmp-Variablen deklarieren, um Rückgabewerte zu speichern, oder diese Funktionen erneut aufrufen und hoffen, dass der Compiler sie entfernt. Dies vermeiden wir. Möchten Sie den Fall debuggen, dass ein Aufrufer die Argumente verwechselt,gcd
damit es nicht die GCD der ersten beiden Argumente ist, die an den Konstruktor übergeben wurden? Nein? Dann machen Sie diesen Fehler nicht möglich.