signification en ligne dans les interfaces de module

24

Considérez le fichier d'en-tête:

class T
{
private:
  int const ID;

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

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

Ou bien:

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

Dans un monde de pré-modules, ces en-têtes peuvent être inclus textuellement dans plusieurs TU sans violations ODR. De plus, étant donné que les fonctions membres impliquées sont relativement petites, le compilateur devrait probablement "inline" (éviter les appels de fonction lors de l'utilisation) de ces fonctions, ou même optimiser loin certaines instances de Ttout à fait.

Dans un récent rapport sur la réunion où C ++ 20 s'est terminé, j'ai pu lire la déclaration suivante:

Nous avons clarifié la signification des inlineinterfaces dans le module: l'intention est que les corps de fonctions qui ne sont pas explicitement déclarés inlinene fassent pas partie de l'ABI d'un module, même si ces corps de fonction apparaissent dans l'interface du module. Afin de donner aux auteurs de modules plus de contrôle sur leur ABI, les fonctions membres définies dans les corps de classe dans les interfaces de module ne le sont plus implicitement inline.

Je ne suis pas sûr de ne pas me tromper. Cela signifie-t-il que, dans un monde de modules, pour que le compilateur puisse optimiser les appels de fonction loin, nous devons les annoter comme inlines'ils étaient définis en classe?

Dans l'affirmative, l'interface de module suivante serait-elle équivalente aux en-têtes ci-dessus?

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

Même si je n'ai toujours pas de compilateur avec prise en charge des modules, j'aimerais commencer à l'utiliser inlinecomme il convient, afin de minimiser la refactorisation future.

metalfox
la source

Réponses:

11

Cela signifie-t-il que, dans un monde de modules, pour que le compilateur puisse optimiser les appels de fonction loin, nous devons les annoter comme inlines'ils étaient définis en classe?

Dans une certaine mesure.

L'insertion est une optimisation "comme si", et l'inclusion peut se produire même entre les unités de traduction si le compilateur est assez intelligent.

Cela étant dit, l'inlining est plus facile lorsque vous travaillez au sein d'une seule unité de traduction. Ainsi, pour favoriser une intégration facile, une inlinefonction déclarée doit avoir sa définition fournie dans n'importe quelle unité de traduction où elle est utilisée. Cela ne signifie pas que le compilateur l'inclura certainement (ou certainement inlinen'inclura aucune fonction non qualifiée), mais cela rend les choses beaucoup plus faciles sur le processus d'inline, car l'inline se produit au sein d'une UT plutôt qu'entre eux.

Les définitions de membres de classe définies dans une classe, dans un monde pré-module, sont déclarées inlineimplicitement. Pourquoi? Parce que la définition est dans la classe. Dans un monde pré-module, les définitions de classe qui sont partagées entre les TU sont partagées par inclusion textuelle. Les membres définis dans une classe seraient donc définis dans l'en-tête partagé entre ces UT. Donc, si plusieurs UT utilisent la même classe, ces multiples UT le font en incluant la définition de classe et la définition de ses membres déclarées dans l'en-tête.

Autrement dit, vous incluez la définition de toute façon , alors pourquoi ne pas la faire inline?

Bien sûr, cela signifie que la définition d'une fonction fait désormais partie du texte de la classe. Si vous modifiez la définition d'un membre déclaré dans un en-tête, cela force la recompilation de chaque fichier qui inclut cet en-tête, récursivement. Même si l'interface de la classe elle-même ne change pas, vous devez toujours effectuer une recompilation. Donc, la création implicite de telles fonctions inlinene change rien à cela, vous pouvez donc tout aussi bien le faire.

Pour éviter cela dans un monde de pré-module, vous pouvez simplement définir le membre dans le fichier C ++, qui ne sera pas inclus dans d'autres fichiers. Vous perdez facilement l'inline, mais vous gagnez du temps de compilation.

Mais voici la chose: c'est un artefact de l'utilisation de l'inclusion textuelle comme moyen de fournir une classe à plusieurs endroits.

Dans un monde modulaire, vous voudrez probablement définir chaque fonction membre au sein de la classe elle-même, comme nous le voyons dans d'autres langages comme Java, C #, Python, etc. Cela maintient la localité du code raisonnable et évite d'avoir à retaper la même signature de fonction, répondant ainsi aux besoins de DRY.

Mais si tous les membres sont définis dans la définition de classe, alors selon les anciennes règles, tous ces membres le seraient inline. Et pour qu'un module permette à une fonction d'être inline, l'artefact du module binaire devrait inclure la définition de ces fonctions. Ce qui signifie que chaque fois que vous modifiez une seule ligne de code dans une telle définition de fonction, le module devra être construit, avec chaque module en fonction, de manière récursive.

La suppression des inlinemodules implicites donne aux utilisateurs les mêmes pouvoirs qu'ils avaient dans les jours d'inclusion textuelle, sans avoir à déplacer la définition hors de la classe. Vous pouvez choisir quelles définitions de fonctions font partie du module et lesquelles ne le sont pas.

Nicol Bolas
la source
8

Cela vient de P1779 , qui vient d'être adopté à Prague il y a quelques jours. De la proposition:

Cet article propose de supprimer l'état implicite en ligne des fonctions définies dans une définition de classe attachée à un module (nommé). Cela permet aux classes de bénéficier d'éviter les déclarations redondantes, tout en conservant la flexibilité offerte aux auteurs de modules pour déclarer des fonctions avec ou sans en ligne. De plus, cela permet aux amis injectés des modèles de classe (qui ne peuvent pas être définis de manière générique en dehors de la définition de classe) d'être non en ligne du tout. Il résout également le commentaire du NB US90 .

Le papier (entre autres choses) a supprimé la phrase:

Une fonction définie dans une définition de classe est une fonction en ligne.

et a ajouté la phrase:

Dans le module global, une fonction définie dans une définition de classe est implicitement en ligne ([class.mfct], [class.friend]).


Votre exemple avec export module Mserait l'équivalent modulaire du programme initial. Notez que les compilateurs font déjà des fonctions en ligne qui ne sont pas annotées inline, c'est juste qu'ils utilisent en plus la présence du inlinemot - clé dans leurs heuristiques.

Barry
la source
Ainsi, une fonction dans un module sans le inlinemot - clé ne sera jamais insérée par le compilateur, n'est-ce pas?
metalfox
1
@metalfox Non, je ne pense pas que ce soit correct.
Barry
1
Je vois. Merci. C'est comme s'il était défini dans un fichier cpp, ce qui ne signifie pas nécessairement qu'il ne sera pas inséré au moment du lien.
metalfox