Inline-Bedeutung in Modulschnittstellen

24

Betrachten Sie die Header-Datei:

class T
{
private:
  int const ID;

public:
  explicit T(int const ID_) noexcept : ID(ID_) {}

  int GetID() const noexcept { return ID; }
};

oder alternativ:

class T
{
private:
  int const ID;

public:
  explicit T(int const ID_) noexcept;

  int GetID() const noexcept;
};

inline T::T(int const ID_) noexcept : ID(ID_) {}

inline int T::GetID() const noexcept { return ID; }

In einer Welt vor den Modulen können diese Header ohne ODR-Verstöße in mehreren TU enthalten sein. Da die beteiligten Elementfunktionen relativ klein sind, würde der Compiler diese Funktionen wahrscheinlich "inline" (Funktionsaufrufe bei der Verwendung vermeiden) oder sogar einige Instanzen Tinsgesamt optimieren .

In einem kürzlich veröffentlichten Bericht über das Meeting, bei dem C ++ 20 beendet wurde, konnte ich die folgende Aussage lesen:

Wir haben die Bedeutung von inlinein Modulschnittstellen geklärt : Es ist beabsichtigt, dass nicht explizit deklarierte Funktionskörper inlinenicht Teil des ABI eines Moduls sind, selbst wenn diese Funktionskörper in der Modulschnittstelle erscheinen. Um den Modulautoren mehr Kontrolle über ihre ABI zu geben, sind in Klassenkörpern in Modulschnittstellen definierte Elementfunktionen nicht mehr implizit inline.

Ich bin mir nicht sicher, ob ich mich nicht irre. Bedeutet das, dass in einer Modulwelt, damit der Compiler Funktionsaufrufe optimieren kann, wir sie so kommentieren müssen, als inlineob sie in der Klasse definiert wären?

Wenn ja, würde die folgende Modulschnittstelle den obigen Headern entsprechen?

export module M;

export
class T
{
private:
  int const ID;

public:
  inline explicit T(int const ID_) noexcept : ID(ID_) {}

  inline int GetID() const noexcept { return ID; }
};

Auch wenn ich noch keinen Compiler mit Modulunterstützung habe, möchte ich ihn inlinegegebenenfalls so verwenden, um das zukünftige Refactoring zu minimieren.

Metallfuchs
quelle

Antworten:

11

Bedeutet das, dass in einer Modulwelt, damit der Compiler Funktionsaufrufe optimieren kann, wir sie so kommentieren müssen, als inlineob sie in der Klasse definiert wären?

Zu einem gewissen Grad.

Inlining ist eine "als ob" -Optimierung, und Inlining kann sogar zwischen Übersetzungseinheiten auftreten, wenn der Compiler klug genug ist.

Allerdings ist Inlining am einfachsten, wenn Sie innerhalb einer einzelnen Übersetzungseinheit arbeiten. Um ein einfaches Inlining zu fördern inline, muss die Definition einer deklarierten Funktion in jeder Übersetzungseinheit angegeben werden, in der sie verwendet wird. Dies bedeutet nicht, dass der Compiler es mit Sicherheit inline (oder mit Sicherheit keine nicht inlinequalifizierte Funktion inline), aber es erleichtert den Inlining-Prozess erheblich, da das Inlining innerhalb einer TU und nicht zwischen ihnen stattfindet.

Klassenmitgliedsdefinitionen, die innerhalb einer Klasse in einer Welt vor dem Modul definiert sind, werden inlineimplizit deklariert . Warum? Weil die Definition innerhalb der Klasse liegt. In einer Welt vor dem Modul werden Klassendefinitionen, die von TUs gemeinsam genutzt werden, durch Texteinschluss gemeinsam genutzt. In einer Klasse definierte Mitglieder würden daher in dem Header definiert, der von diesen TUs gemeinsam genutzt wird. Wenn also mehrere TUs dieselbe Klasse verwenden, tun diese mehreren TUs dies, indem sie die Klassendefinition und die Definition ihrer Mitglieder in den Header aufnehmen.

Das heißt, Sie schließen die Definition trotzdem ein . Warum also nicht inline?

Dies bedeutet natürlich, dass die Definition einer Funktion jetzt Teil des Textes der Klasse ist. Wenn Sie die Definition eines in einem Header deklarierten Elements ändern, wird die rekursive Neukompilierung aller Dateien, die diesen Header enthalten, erzwungen. Auch wenn sich die Benutzeroberfläche der Klasse selbst nicht ändert, müssen Sie dennoch eine Neukompilierung durchführen. Wenn Sie solche Funktionen implizit inlineausführen, ändert sich dies nicht. Sie können dies also auch tun.

Um dies in einer Welt vor dem Modul zu vermeiden, können Sie einfach das Mitglied in der C ++ - Datei definieren, das nicht in andere Dateien aufgenommen wird. Sie verlieren leichtes Inlining, gewinnen aber Kompilierungszeit.

Aber hier ist die Sache: Dies ist ein Artefakt der Verwendung von Texteinschluss als Mittel, um eine Klasse an mehrere Orte zu liefern.

In einer modularen Welt möchten Sie wahrscheinlich jede Elementfunktion innerhalb der Klasse selbst definieren, wie wir in anderen Sprachen wie Java, C #, Python und dergleichen sehen. Dies hält die Codelokalität angemessen und verhindert, dass dieselbe Funktionssignatur erneut eingegeben werden muss, wodurch die Anforderungen von DRY erfüllt werden.

Wenn jedoch alle Mitglieder innerhalb der Klassendefinition definiert sind, wären dies nach den alten Regeln alle diese Mitglieder inline. Und damit ein Modul eine Funktion zulässt inline, müsste das Artefakt des Binärmoduls die Definition dieser Funktionen enthalten. Dies bedeutet, dass jedes Mal, wenn Sie in einer solchen Funktionsdefinition auch nur eine Codezeile ändern, das Modul zusammen mit jedem davon abhängigen Modul rekursiv erstellt werden muss.

Durch das Entfernen impliziter inlineModule erhalten Benutzer die gleichen Befugnisse wie in den Tagen der Textaufnahme, ohne dass die Definition aus der Klasse verschoben werden muss. Sie können auswählen, welche Funktionsdefinitionen Teil des Moduls sind und welche nicht.

Nicol Bolas
quelle
8

Dies kommt von P1779 , das vor einigen Tagen in Prag verabschiedet wurde. Aus dem Vorschlag:

In diesem Dokument wird vorgeschlagen, den impliziten Inline-Status aus Funktionen zu entfernen, die in einer Klassendefinition definiert sind, die an ein (benanntes) Modul angehängt ist. Auf diese Weise können Klassen von der Vermeidung redundanter Deklarationen profitieren und die Flexibilität beibehalten, die Modulautoren bei der Deklaration von Funktionen mit oder ohne Inline bietet. Darüber hinaus können injizierte Freunde von Klassenvorlagen (die außerhalb der Klassendefinition nicht generisch definiert werden können) überhaupt nicht inline sein. Es löst auch NB Kommentar US90 .

Das Papier entfernte (unter anderem) den Satz:

Eine innerhalb einer Klassendefinition definierte Funktion ist eine Inline-Funktion.

und fügte den Satz hinzu:

Im globalen Modul ist eine in einer Klassendefinition definierte Funktion implizit inline ([class.mfct], [class.friend]).


Dein Beispiel mit export module M wäre das modulare Äquivalent des ursprünglichen Programms. Beachten Sie, dass Compiler bereits Inline-Funktionen ausführen, die nicht mit Anmerkungen versehen inlinesind. Sie verwenden lediglich das Vorhandensein des inlineSchlüsselworts in ihren Heuristiken.

Barry
quelle
Eine Funktion in einem Modul ohne das inlineSchlüsselwort wird also niemals vom Compiler eingefügt, richtig?
Metalfox
1
@metalfox Nein, ich glaube nicht, dass das richtig ist.
Barry
1
Aha. Vielen Dank. Es ist wie in einer CPP-Datei definiert, was nicht unbedingt bedeutet, dass es zum Zeitpunkt der Verknüpfung nicht eingefügt wird.
Metalfox