Teilen über


Inlinefunktionen (C++)

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

Die Verwendung von Inlinefunktionen kann theoretisch das Programm schneller machen, da so der Mehraufwand vermieden wird, der Funktionsaufrufen zugeordnet ist. Zum Aufrufen einer Funktion muss die Absenderadresse in den Stapel gelegt werden, Argumente in den Stapel gelegt werden, zum Funktionskörper gesprungen werden und nach Abschluss der Funktion eine Return-Anweisung ausgeführt werden. Dieser Prozess wird durch das Inlining 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 fügt der Compiler eine Funktion nicht ein, wenn ihre Adresse bereits vergeben ist oder wenn der Compiler entscheidet, dass sie zu groß ist.

Eine im Hauptteil einer Klassendeklaration definierte Funktion ist implizit eine Inline-Funktion.

Beispiel

In der folgenden Klassendeklaration ist der Account Konstruktor eine Inlinefunktion, da er im Textkörper der Klassendeklaration definiert ist. Die Memberfunktionen GetBalance, Deposit und Withdraw werden in ihren Definitionen inline angegeben. Das inline Schlüsselwort 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 das inline-Schlüsselwort deklariert. Das inline Schlüsselwort inline kann in der Klassendeklaration bezeichnet werden; das Ergebnis ist dasselbe.

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

Eine Klassenmemberfunktion führt standardmäßig zu externer Bindung, es sei denn, eine Definition für diese Funktion enthält den inline-Spezifizierer. Im vorherigen Beispiel wird gezeigt, dass Sie diese Funktionen nicht explizit mit dem inline-Spezifizierer deklarieren müssen. Die Verwendung von inline in der Funktionsdefinition schlägt dem Compiler vor, dass er als Inlinefunktion behandelt wird. Sie können eine Funktion jedoch nicht als inline nach einem Aufruf dieser Funktion neu definieren.

inline, __inline und __forceinline

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

Die Einfügung (bezeichnet als Inlineerweiterung oder Inlinekonstrukt) wird nur ausgeführt, wenn die Kosten-Nutzen-Analyse des Compilers dies als sinnvoll bewertet. Eine Inlineerweiterung minimiert den Funktionsaufruf-Mehraufwand, möglicherweise auf Kosten der größeren Codegröße.

Das __forceinline-Schlüsselwort überschreibt die Kosten-Nutzen-Analyse und basiert stattdessen auf dem Urteil des Programmierers. Sie sollten __forceinline mit Vorsicht verwenden. Die wahllose Verwendung von __forceinline kann zu längerem Code mit nur marginalen Leistungssteigerungen oder in einigen Fällen sogar mit Leistungsverlusten führen (beispielsweise aufgrund des erweiterten Pagings einer größeren ausführbaren Datei).

Der Compiler behandelt die Inlineerweiterungsoptionen und -Schlüsselwörter als Vorschläge. Es gibt keine Garantie, dass Funktionen inline gestellt werden. Sie können den Compiler nicht zwingen, eine bestimmte Funktion inline zu setzen, auch nicht mit dem __forceinline-Schlüsselwort. Beim Kompilieren mit /clr setzt der Compiler eine Funktion nicht inline, wenn auf die Funktion Sicherheitsattribute 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.

Das inline-Schlüsselwort inline teilt dem Compiler mit, dass eine Inlineerweiterung bevorzugt wird. Der Compiler kann dies 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 und andere Gründe können je nach Feststellung des Compilers die Inlining-Erstellung beeinträchtigen. Verlassen Sie sich nicht darauf, dass der inline-Spezifizierer eine Funktion inline einfügt.

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 Inlining durch, unabhängig davon, ob es 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 indem das inline-Schlüsselwort verwendet oder die Funktionsdefinition innerhalb der Klassendefinition platziert wird.

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

Das __inline-Schlüsselwort ist äquivalent zu inline.

Der Compiler kann sogar mit __forceinline keine Funktion inline setzen, wenn Folgendes der Fall ist:

  • Die Funktion oder ihr Aufrufer werden mit /Ob0 kompiliert (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 eine Inlineassembly, es sei denn, sie wird mit /Ox, /O1, oder /O2 kompiliert.
  • Die Funktion ist rekursiv und hat #pragma inline_recursion(on) nicht 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 Funktion, die mit __forceinline deklariert ist, nicht inline stellen kann, wird eine Warnung der Stufe 1 ausgelöst, es sei denn:

  • Die Funktion wird mithilfe von /Od oder /Ob0 kompiliert. In diesen Fällen wird kein Inlining 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 Code ohne Inlining 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, bis zu der rekursive Funktionen durch die Inlineheuristik geprüft werden, kann 16 nicht überschreiten. Das inline_recursion-Pragma steuert die Inlineerweiterung einer Funktion, die aktuell erweitert wird. Weitere Informationen erhalten Sie unter der (/Ob) Compileroption Inlinefunktionserweiterung.

Ende Microsoft-spezifisch

Weitere Informationen zum Verwenden 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 benötigen proportional weniger Zeit in der Aufruf- und Rückgabesequenz und profitieren weniger vom Inlining.

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;
}

Die Koordinatenmanipulation ist ein relativ allgemeiner Vorgang in einem Client dieser Klasse und durch die Angabe der beiden Zugriffsmethodenfunktionen (x und y im vorhergehenden Beispiel) als inline wird in der Regel der Mehraufwand gespart bei:

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

Inlinefunktionen im Vergleich zu Makros

Ein Makro hat einige Dinge, die mit einer inline Funktion gemeinsam sind. Es gibt jedoch zwei 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) auf 2 + 2 * 3 + 3 erweitert, der auf 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 zu #define mult2(a, b) (a) * (b) führt, dass das Problem löst, aber dennoch überraschendes Verhalten verursachen kann, wenn es Teil eines größeren Ausdrucks ist. Dies wurde im vorherigen Beispiel veranschaulicht, und das Problem konnte durch Definieren des Makros wie #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, Funktionen jedoch schon.
  • 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))) erweitert wird. Dies führte dazu, dass der zweite Aufruf von increment 6 zurückgibt, 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.

Siehe auch

noinline
auto_inline