Inlinefunktionen (C++)

Der inline Schlüsselwort (keyword) schlägt vor, dass der Compiler anstelle jedes Aufrufs dieser Funktion den Code innerhalb der Funktionsdefinition ersetzt.

Theoretisch kann die Verwendung von Inlinefunktionen Ihr Programm schneller machen, da sie den mit Funktionsaufrufen verbundenen Aufwand beseitigen. Zum Aufrufen einer Funktion muss die Absenderadresse im Stapel verschoben, Argumente auf den Stapel verschoben, zum Funktionstext springen und dann eine Rückgabeanweisung ausgeführt werden, wenn die Funktion abgeschlossen ist. Dieser Prozess wird durch das Eingliedern der Funktion eliminiert. Der Compiler bietet auch unterschiedliche Möglichkeiten zum Optimieren erweiterter Inlinefunktionen im Vergleich zu denen, die nicht vorhanden sind. Ein Kompromiss von Inlinefunktionen besteht darin, dass die Gesamtgröße Ihres Programms erhöht werden kann.

Die Inlinecodeersetzung erfolgt nach Ermessen des Compilers. Beispielsweise wird der Compiler keine Funktion inlineieren, wenn seine Adresse verwendet wird oder der Compiler entscheidet, dass er zu groß ist.

Eine im Textkörper einer Klassendeklaration definierte Funktion ist implizit eine Inlinefunktion.

Beispiel

In der folgenden Klassendeklaration ist der Account Konstruktor eine Inlinefunktion, da er im Textkörper der Klassendeklaration definiert ist. Die Memberfunktionen GetBalance, Depositund Withdraw werden in ihren Definitionen angegeben inline . Die inline Schlüsselwort (keyword) ist in den Funktionsdeklarationen in der Klassendeklaration optional.

// account.h
class Account
{
public:
    Account(double initial_balance)
    {
        balance = initial_balance;
    }

    double GetBalance() const;
    double Deposit(double amount);
    double Withdraw(double amount);

private:
    double balance;
};

inline double Account::GetBalance() const
{
    return balance;
}

inline double Account::Deposit(double amount)
{
    balance += amount;
    return balance;
}

inline double Account::Withdraw(double amount)
{
    balance -= amount;
    return balance;
}

Hinweis

In der Klassendeklaration wurden die Funktionen ohne die inline Schlüsselwort (keyword) deklariert. Die inline Schlüsselwort (keyword) können in der Klassendeklaration angegeben werden. Das Ergebnis ist identisch.

Eine angegebene Inlinememberfunktion muss in jeder Kompilierungseinheit auf die gleiche Weise deklariert werden. Es muss genau eine Definition einer Inlinefunktion vorhanden sein.

Eine Klassenmememmungsfunktion weist standardmäßig externe Verknüpfungen auf, es sei denn, eine Definition für diese Funktion enthält den inline Bezeichner. Im vorherigen Beispiel wird gezeigt, dass Sie diese Funktionen nicht explizit mit dem inline Bezeichner deklarieren müssen. Die Verwendung inline in der Funktionsdefinition schlägt dem Compiler vor, dass er als Inlinefunktion behandelt wird. Sie können eine Funktion jedoch nicht wie inline nach einem Aufruf dieser Funktion neu definieren.

inline, __inline und __forceinline

Die inline Bezeichner __inline schlagen dem Compiler vor, eine Kopie des Funktionstexts an jede Stelle einzufügen, an der die Funktion aufgerufen wird.

Die Einfügung, die als Inlineerweiterung oder Inlining bezeichnet wird, tritt nur auf, wenn die eigene Kosten-Nutzen-Analyse des Compilers zeigt, dass es sich lohnt. Die Inlineerweiterung minimiert den Funktionsaufrufaufwand bei den potenziellen Kosten einer größeren Codegröße.

Die __forceinline Schlüsselwort (keyword) setzt die Kosten-Nutzen-Analyse außer Kraft und basiert stattdessen auf dem Urteil des Programmierers. Seien Sie vorsichtig, wenn Sie es verwenden __forceinline. Die wahllose Verwendung __forceinline kann zu einem größeren Code führen, der nur geringfügige Leistungsgewinne oder in einigen Fällen sogar Leistungsverluste (z. B. aufgrund der erhöhten Auslagerung einer größeren ausführbaren Datei) zur Folge hat.

Der Compiler behandelt die Inlineerweiterungsoptionen und -Schlüsselwörter als Vorschläge. Es gibt keine Garantie dafür, dass Funktionen inlineiert werden. Sie können nicht erzwingen, dass der Compiler eine bestimmte Funktion inlineiert, auch nicht mit dem __forceinline Schlüsselwort (keyword). Wenn Sie kompilieren, /clrwird der Compiler eine Funktion nicht inlineieren, wenn sicherheitsrelevante Attribute auf die Funktion angewendet werden.

Für die Kompatibilität mit früheren Versionen sind _inline_forceinline Synonyme für __inlinebzw. __forceinline, sofern die Compileroption /Za (Spracherweiterungen deaktivieren) nicht angegeben ist.

Der inline Schlüsselwort (keyword) teilt dem Compiler mit, dass die Inlineerweiterung bevorzugt wird. Der Compiler kann ihn jedoch ignorieren. Zwei Fälle, in denen dieses Verhalten auftreten kann, sind:

  • Rekursive Funktionen.
  • Funktionen, auf die durch einen Zeiger an anderer Stelle in der Übersetzungseinheit verwiesen wird.

Diese Gründe können die Inlinierung beeinträchtigen, wie andere, wie sie vom Compiler bestimmt werden. Hängen Sie nicht vom inline Bezeichner ab, damit eine Funktion inlineiert wird.

Anstatt eine in einer Headerdatei definierte Inlinefunktion zu erweitern, erstellt der Compiler sie möglicherweise als aufrufbare Funktion in mehr als einer Übersetzungseinheit. Der Compiler kennzeichnet die generierte Funktion für den Linker, um OdR-Verletzungen (One-Definition-Rule) zu verhindern.

Wie bei normalen Funktionen gibt es keine definierte Reihenfolge für die Argumentauswertung in einer Inlinefunktion. Tatsächlich kann es sich von der Argumentauswertungsreihenfolge unterscheiden, wenn sie mithilfe des normalen Funktionsaufrufprotokolls übergeben wird.

Verwenden Sie die /Ob Compileroptimierungsoption, um zu beeinflussen, ob die Inlinefunktionserweiterung tatsächlich auftritt.
/LTCG führt modulübergreifendes Eingrenzen davon aus, ob sie im Quellcode angefordert wird oder nicht.

Beispiel 1

// inline_keyword1.cpp
// compile with: /c
inline int max(int a, int b)
{
    return a < b ? b : a;
}

Die Memberfunktionen einer Klasse können inline deklariert werden, entweder mithilfe des inline Schlüsselwort (keyword) oder durch Platzieren der Funktionsdefinition in der Klassendefinition.

Beispiel 2

// inline_keyword2.cpp
// compile with: /EHsc /c
#include <iostream>

class MyClass
{
public:
    void print() { std::cout << i; }   // Implicitly inline

private:
    int i;
};

Microsoft-spezifisch

Die __inline Schlüsselwort (keyword) entspricht inline.

Selbst mit __forceinline, kann der Compiler eine Funktion nicht inlineieren, wenn:

  • Die Funktion oder der Aufrufer werden kompiliert mit /Ob0 (die Standardoption für Debugbuilds).
  • Die Funktion und der Aufrufer verwenden unterschiedliche Typen der Ausnahmebehandlung (C++-Ausnahmebehandlung zum einen, strukturierte Ausnahmebehandlung zum anderen).
  • Die Funktion weist eine variable Argumentliste auf.
  • Die Funktion verwendet Inlineassembly, es sei denn, kompiliert mit /Ox, oder /O1/O2.
  • Die Funktion ist rekursiv und hat #pragma inline_recursion(on) keinen Satz festgelegt. Mit dem Pragma werden rekursive Funktionen mit einer Standardtiefe von 16 Aufrufen inline gesetzt. Verwenden Sie inline_depth pragma, um die Inliningtiefe zu reduzieren.
  • Die Funktion ist virtuell und wird virtuell aufgerufen. Direkte Aufrufe virtueller Funktionen können inline gesetzt werden.
  • Das Programm akzeptiert die Adresse der Funktion, und der Aufruf erfolgt über den Zeiger auf die Funktion. Direkte Aufrufe von Funktionen, deren Adresse akzeptiert wurden, können inline gesetzt werden.
  • Die Funktion ist auch mit dem naked__declspec Modifizierer gekennzeichnet.

Wenn der Compiler eine mit __forceinlineeiner Funktion deklarierte Funktion nicht inlineieren kann, wird eine Warnung der Ebene 1 generiert, außer wenn:

  • Die Funktion wird mithilfe von /Od oder /Ob0 kompiliert. In diesen Fällen wird keine Inlinierung erwartet.
  • Die Funktion wird extern, in einer enthaltenen Bibliothek oder einer anderen Übersetzungseinheit definiert oder ist ein virtuelles Anrufziel oder ein indirektes Anrufziel. Der Compiler kann keinen nicht inlineierten Code identifizieren, den er in der aktuellen Übersetzungseinheit nicht finden kann.

Rekursive Funktionen können durch Inlinecode in eine durch das inline_depth Pragma angegebene Tiefe ersetzt werden, bis zu maximal 16 Aufrufe. Nach dieser Tiefe werden rekursive Funktionsaufrufe als Aufrufe einer Instanz der Funktion behandelt. Die Tiefe, auf die rekursive Funktionen durch die Inline heuristik untersucht werden, darf 16 nicht überschreiten. Das inline_recursion Pragma steuert die Inlineerweiterung einer Funktion, die derzeit erweitert wird. Weitere Informationen finden Sie in der Compileroption Inline-Funktionserweiterung (/Ob).

Ende Microsoft-spezifisch

Weitere Informationen zur Verwendung des inline Bezeichners finden Sie unter:

Verwendungsmöglichkeiten von Inlinefunktionen

Inlinefunktionen werden am besten für kleine Funktionen verwendet, z. B. für Funktionen, die Zugriff auf Datenmmber ermöglichen. Kurze Funktionen sind für den Aufwand von Funktionsaufrufen sensibel. Längere Funktionen verbringen in der Aufrufen- und Rückgabesequenz proportional weniger Zeit und profitieren weniger von der Inlinierung.

Eine Point Klasse kann wie folgt definiert werden:

// when_to_use_inline_functions.cpp
// compile with: /c
class Point
{
public:
    // Define "accessor" functions
    // as reference types.
    unsigned& x();
    unsigned& y();

private:
    unsigned _x;
    unsigned _y;
};

inline unsigned& Point::x()
{
    return _x;
}

inline unsigned& Point::y()
{
    return _y;
}

Bei der Annahme der Koordinatenmanipulation handelt es sich um einen relativ gemeinsamen Vorgang in einem Client einer solchen Klasse, wobei die beiden Accessorfunktionen (x und y im vorherigen Beispiel) angegeben werden, da inline in der Regel der Aufwand gespart wird:

  • Funktionsaufrufen (einschließlich der Parameterübergabe und -ablage der Adresse des Objekts auf dem Stapel)
  • Beibehaltung des Stapelrahmens des Aufrufers
  • Einrichtung eines neuen Stapelrahmens
  • Rückgabewertkommunikation
  • Wiederherstellen des alten Stapelrahmens
  • Return

Inlinefunktionen im Vergleich zu Makros

Ein Makro hat einige Dinge, die mit einer inline Funktion gemeinsam sind. Aber es gibt wichtige Unterschiede. Betrachten Sie das folgende Beispiel:

#include <iostream>

#define mult1(a, b) a * b
#define mult2(a, b) (a) * (b)
#define mult3(a, b) ((a) * (b))

inline int multiply(int a, int b)
{
    return a * b;
}

int main()
{
    std::cout << (48 / mult1(2 + 2, 3 + 3)) << std::endl; // outputs 33
    std::cout << (48 / mult2(2 + 2, 3 + 3)) << std::endl; // outputs 72
    std::cout << (48 / mult3(2 + 2, 3 + 3)) << std::endl; // outputs 2
    std::cout << (48 / multiply(2 + 2, 3 + 3)) << std::endl; // outputs 2

    std::cout << mult3(2, 2.2) << std::endl; // no warning
    std::cout << multiply(2, 2.2); // Warning C4244	'argument': conversion from 'double' to 'int', possible loss of data
}
33
72
2
2
4.4
4

Hier sind einige der Unterschiede zwischen dem Makro und der Inlinefunktion:

  • Makros werden immer inline erweitert. Eine Inlinefunktion wird jedoch nur inlineiert, wenn der Compiler feststellt, dass es die optimale Aufgabe ist.
  • Das Makro kann zu unerwartetem Verhalten führen, was zu subtilen Fehlern führen kann. Beispielsweise wird der Ausdruck mult1(2 + 2, 3 + 3) erweitert, auf 2 + 2 * 3 + 3 den 11 ausgewertet wird, aber das erwartete Ergebnis ist 24. Eine scheinbar gültige Lösung besteht darin, Klammern um beide Argumente des Funktionsmakros hinzuzufügen, was dazu #define mult2(a, b) (a) * (b)führt, dass das Problem gelöst wird, aber dennoch überraschendes Verhalten verursachen kann, wenn Ein Teil eines größeren Ausdrucks ist. Dies wurde im vorherigen Beispiel veranschaulicht, und das Problem konnte durch Definieren des Makros als solche #define mult3(a, b) ((a) * (b))behoben werden.
  • Eine Inlinefunktion unterliegt der semantischen Verarbeitung durch den Compiler, während der Präprozessor Makros ohne denselben Vorteil erweitert. Makros sind nicht typsicher, während Funktionen sind.
  • Die Ausdrücke, die als Argumente an Inlinefunktionen übergeben werden, werden einmal ausgewertet. In einigen Fällen können die Ausdrücke, die als Argumente an Makros übergeben werden, mehrmals ausgewertet werden. Berücksichtigen Sie beispielsweise Folgendes:
#include <iostream>

#define sqr(a) ((a) * (a))

int increment(int& number)
{
    return number++;
}

inline int square(int a)
{
    return a * a;
}

int main()
{
    int c = 5;
    std::cout << sqr(increment(c)) << std::endl; // outputs 30
    std::cout << c << std::endl; // outputs 7

    c = 5;
    std::cout << square(increment(c)) << std::endl; // outputs 25
    std::cout << c; // outputs 6
}
30
7
25
6

In diesem Beispiel wird die Funktion increment zweimal aufgerufen, während der Ausdruck sqr(increment(c)) auf ((increment(c)) * (increment(c))). Dies führte dazu, dass der zweite Aufruf von increment 6 zurückgegeben wurde, daher wird der Ausdruck auf 30 ausgewertet. Jeder Ausdruck, der Nebenwirkungen enthält, kann sich auf das Ergebnis auswirken, wenn es in einem Makro verwendet wird, das vollständig erweiterte Makro überprüfen, um zu überprüfen, ob das Verhalten beabsichtigt ist. Wenn stattdessen die Inlinefunktion square verwendet wurde, wird die Funktion increment nur einmal aufgerufen, und das richtige Ergebnis von 25 wird abgerufen.

Weitere Informationen

noinline
auto_inline