Pourquoi les fonctions en ligne C ++ sont-elles dans l'en-tête?

120

NB Il ne s'agit pas de savoir comment utiliser les fonctions en ligne ou comment elles fonctionnent, mais plutôt pourquoi elles sont faites telles qu'elles sont.

La déclaration d'une fonction membre de classe n'a pas besoin de définir une fonction car inlineil s'agit uniquement de l'implémentation réelle de la fonction. Par exemple, dans le fichier d'en-tête:

struct foo{
    void bar(); // no need to define this as inline
}

Alors pourquoi l'implémentation en ligne d'une fonction de classes doit-elle se trouver dans le fichier d'en-tête? Pourquoi ne puis-je pas mettre la fonction en ligne dans le .cppfichier? Si je devais essayer de mettre la définition en ligne dans le .cppfichier, j'obtiendrais une erreur du type:

error LNK2019: unresolved external symbol 
"public: void __thiscall foo::bar(void)"
(?bar@foo@@QAEXXZ) referenced in function _main 
1>C:\Users\Me\Documents\Visual Studio 2012\Projects\inline\Debug\inline.exe 
: fatal error LNK1120: 1 unresolved externals
thecoshman
la source
@Charles Je dirais que ce deuxième lien est similaire, mais je demande plus sur la logique derrière pourquoi l'inline fonctionne comme il le fait.
thecoshman
2
Dans ce cas, je pense que vous avez peut-être mal compris les "fichiers en ligne" ou les "fichiers d'en-tête"; aucune de vos affirmations n'est vraie. Vous pouvez avoir une implémentation en ligne d'une fonction membre et vous pouvez mettre des définitions de fonction en ligne dans un fichier d'en-tête, ce n'est peut-être pas une bonne idée. Pouvez-vous clarifier votre question?
CB Bailey
Après modification, je pense que vous vous posez peut-être des questions sur les situations où inlineapparaît sur une définition mais pas sur une déclaration préalable vs vice versa . Si tel est le cas, cela peut aider: stackoverflow.com/questions/4924912/…
CB Bailey

Réponses:

122

La définition d'une inlinefonction n'a pas besoin d'être dans un fichier d'en-tête mais, en raison de la règle de définition unique ( ODR ) pour les fonctions en ligne, une définition identique pour la fonction doit exister dans chaque unité de traduction qui l'utilise.

Le moyen le plus simple d'y parvenir est de placer la définition dans un fichier d'en-tête.

Si vous souhaitez mettre la définition d'une fonction dans un seul fichier source, vous ne devez pas la déclarer inline. Une fonction non déclarée inlinene signifie pas que le compilateur ne peut pas intégrer la fonction.

Que vous deviez déclarer une fonction inlineou non est généralement un choix que vous devez faire en fonction de la version des règles de définition unique qu'il est le plus logique pour vous de suivre; ajouter inlinepuis être restreint par les contraintes ultérieures n'a pas de sens.

CB Bailey
la source
Mais le compilateur ne compile pas le fichier .cpp, qui inclut les fichiers .h ... de sorte que lorsqu'il compile un fichier .cpp, il a à la fois la décélération ainsi que les fichiers source. Les autres fichiers d'en-tête extraits ne sont que leurs afin que le compilateur puisse `` avoir confiance '' que ces fonctions existent et seront implémentées dans un autre fichier source
thecoshman
1
C'est en fait une bien meilleure réponse que la mienne, +1de ma part!
sbi
2
@thecoshman: Il y a deux distinctions. Fichier source vs fichier d'en-tête. Par convention, un fichier d'en-tête fait généralement référence à un fichier source qui n'est pas cette base pour une unité de traduction, mais qui n'est #inclu que d'autres fichiers source. Ensuite, il y a déclaration vs définition. Vous pouvez avoir des déclarations ou des définitions de fonctions dans des fichiers d'en-tête ou des fichiers sources «normaux». J'ai bien peur de ne pas savoir ce que vous demandez dans votre commentaire.
CB Bailey
ne vous inquiétez pas, je comprends pourquoi c'est maintenant ... même si je ne sais pas qui a vraiment répondu à celui-ci. Une combinaison de la vôtre et de la réponse de @ Xanatos m'a expliqué.
thecoshman
113

Il y a deux façons de l'examiner:

  1. Les fonctions en ligne sont définies dans l'en-tête car, pour insérer un appel de fonction, le compilateur doit pouvoir voir le corps de la fonction. Pour qu'un compilateur naïf fasse cela, le corps de la fonction doit être dans la même unité de traduction que l'appel. (Un compilateur moderne peut optimiser toutes les unités de traduction, et donc un appel de fonction peut être incorporé même si la définition de fonction est dans une unité de traduction séparée, mais ces optimisations sont coûteuses, ne sont pas toujours activées et n'ont pas toujours été prises en charge par le compilateur)

  2. les fonctions définies dans l'en-tête doivent être marquées inlinecar sinon, chaque unité de traduction qui comprend l'en-tête contiendra une définition de la fonction, et l'éditeur de liens se plaindra de plusieurs définitions (une violation de la règle d'une définition). Le inlinemot-clé supprime cela, permettant à plusieurs unités de traduction de contenir des définitions (identiques).

Les deux explications se résument vraiment au fait que le inlinemot-clé ne fait pas exactement ce que vous attendez.

Un compilateur C ++ est libre d'appliquer l' optimisation en ligne (remplacer un appel de fonction par le corps de la fonction appelée, en économisant la surcharge d'appel) à tout moment, tant que cela ne modifie pas le comportement observable du programme.

Le inlinemot-clé facilite l'application de cette optimisation par le compilateur, en permettant à la définition de la fonction d'être visible dans plusieurs unités de traduction, mais l'utilisation du mot-clé ne signifie pas que le compilateur doit intégrer la fonction, et ne pas utiliser le mot-clé interdire au compilateur d'insérer la fonction.

jalf
la source
23

Il s'agit d'une limite du compilateur C ++. Si vous mettez la fonction dans l'en-tête, tous les fichiers cpp où elle peut être insérée peuvent voir la "source" de votre fonction et l'inlining peut être fait par le compilateur. Sinon, l'inlining devrait être fait par l'éditeur de liens (chaque fichier cpp est compilé dans un fichier obj séparément). Le problème est qu'il serait beaucoup plus difficile de le faire dans l'éditeur de liens. Un problème similaire existe avec les classes / fonctions "modèles". Ils doivent être instanciés par le compilateur, car l'éditeur de liens aurait des problèmes pour les instancier (en créant une version spécialisée). Certains compilateurs / éditeurs de liens plus récents peuvent faire une compilation / liaison en "deux passes" où le compilateur fait une première passe, puis l'éditeur de liens fait son travail et appelle le compilateur pour résoudre les choses non résolues (inline / templates ...)

xanatos
la source
Oh je vois! oui, ce n'est pas pour la classe elle-même qui utilise la fonction en ligne, son autre code qui utilise les fonctions en ligne. Ils ne voient que le fichier d'en-tête de la classe en cours d'insertion!
thecoshman
11
Je ne suis pas d'accord avec cette réponse, ce n'est pas une limite de compilateur C ++; c'est purement la manière dont les règles linguistiques sont spécifiées. Les règles de langage autorisent un modèle de compilation simple mais n'interdisent pas d'autres implémentations.
CB Bailey
3
Je suis d'accord avec @Charles. En fait, il existe des compilateurs qui fonctionnent en ligne entre les unités de traduction, donc ce n'est certainement pas dû aux limitations du compilateur.
sbi
5
Bien que cette réponse semble contenir des erreurs techniques, elle m'a aidé à voir comment le compilateur fonctionne avec les fichiers d'en-tête et autres.
thecoshman
10

La raison est que le compilateur doit réellement voir la définition afin de pouvoir la déposer à la place de l'appel.

Rappelez-vous que C et C ++ utilisent un modèle de compilation très simpliste, où le compilateur ne voit toujours qu'une seule unité de traduction à la fois. (Cela échoue pour l'exportation, ce qui est la principale raison pour laquelle un seul fournisseur l'a mis en œuvre.)

sbi
la source
9

Le inlinemot-clé c ++ est trompeur, il ne signifie pas "en ligne cette fonction". Si une fonction est définie comme inline, cela signifie simplement qu'elle peut être définie plusieurs fois tant que toutes les définitions sont égales. Il est parfaitement légal pour une fonction marquée inlined'être une fonction réelle qui est appelée au lieu d'obtenir du code incorporé au point où elle est appelée.

La définition d'une fonction dans un fichier d'en-tête est nécessaire pour les modèles, car par exemple, une classe basée sur un modèle n'est pas vraiment une classe, c'est un modèle pour une classe dont vous pouvez faire plusieurs variantes. Pour que le compilateur puisse par exemple créer une Foo<int>::bar()fonction lorsque vous utilisez le modèle Foo pour créer une classe Foo , la définition réelle de Foo<T>::bar()doit être visible.

Erik
la source
Et comme il s'agit d'un modèle pour une classe , on ne l'appelle pas une classe de modèle , mais un modèle de classe .
sbi
4
Le premier paragraphe a tout à fait raison (et j'aurais aimé pouvoir insister sur «trompeur»), mais je ne vois pas la nécessité du non sequitur dans les modèles.
Thomas Edleson
Certains compilateurs l'utiliseront pour indiquer que la fonction peut probablement être insérée, mais en fait, elle n'est pas garantie d'être insérée simplement parce que vous la déclarez inline(et ne la déclarez pas non plus qu'elle inlinene sera pas insérée).
Keith M
4

Je sais que c'est un vieux fil mais j'ai pensé que je devrais mentionner que le externmot - clé. J'ai récemment rencontré ce problème et résolu comme suit

Helper.h

namespace DX
{
    extern inline void ThrowIfFailed(HRESULT hr);
}

Helper.cpp

namespace DX
{
    inline void ThrowIfFailed(HRESULT hr)
    {
        if (FAILED(hr))
        {
            std::stringstream ss;
            ss << "#" << hr;
            throw std::exception(ss.str().c_str());
        }
    }
}
flammewave000
la source
6
Cela n'entraînera généralement pas l'intégration de la fonction à moins que vous n'utilisiez l'optimisation complète du programme (WPO).
Chuck Walbourn
3

Parce que le compilateur a besoin de les voir pour les incorporer . Et les fichiers d'en-tête sont les «composants» qui sont généralement inclus dans d'autres unités de traduction.

#include "file.h"
// Ok, now me (the compiler) can see the definition of that inline function. 
// So I'm able to replace calls for the actual implementation.
Leandro TC Melo
la source
1

Fonctions en ligne

En C ++, une macro n'est rien d'autre qu'une fonction en ligne. Donc maintenant les macros sont sous le contrôle du compilateur.

  • Important : si nous définissons une fonction à l'intérieur de la classe, elle deviendra automatiquement Inline

Le code de la fonction Inline est remplacé à l'endroit où il est appelé, ce qui réduit la surcharge de la fonction d'appel.

Dans certains cas, l'intégration de la fonction ne peut pas fonctionner, comme

  • Si une variable statique est utilisée dans une fonction en ligne.

  • Si la fonction est compliquée.

  • Si appel récursif de fonction

  • Si l'adresse de la fonction prise implicitement ou explicitement

La fonction définie en dehors de la classe comme ci-dessous peut devenir en ligne

inline int AddTwoVar(int x,int y); //This may not become inline 

inline int AddTwoVar(int x,int y) { return x + y; } // This becomes inline

La fonction définie à l'intérieur de la classe devient également en ligne

// Inline SpeedMeter functions
class SpeedMeter
{
    int speed;
    public:
    int getSpeed() const { return speed; }
    void setSpeed(int varSpeed) { speed = varSpeed; }
};
int main()
{
    SpeedMeter objSM;
    objSM.setSpeed(80);
    int speedValue = A.getSpeed();
} 

Ici, les fonctions getSpeed ​​et setSpeed ​​deviendront en ligne

Saurabh Raoot
la source
eh, quelques informations intéressantes peut-être, mais n'essaient pas vraiment d'expliquer pourquoi . Peut-être que vous le faites, mais ne le dites pas clairement.
thecoshman
2
La déclaration suivante n'est pas vraie: "Important: si nous définissons une fonction à l'intérieur d'une classe, elle deviendra automatiquement Inline" Même si vous écrivez "inline" dans la déclaration / définition, vous pouvez être sûr qu'elle est en fait en ligne. Pas même pour les modèles. Peut-être que vous vouliez dire que le compilateur assume automatiquement le mot-clé "inline", mais n'a pas à suivre et ce que j'ai remarqué, c'est que dans la plupart des cas, il n'inclut pas de telles définitions d'en-tête, même pas pour les fonctions constexpr simples avec arithmétique de base.
Pablo Ariel
Hé merci pour les commentaires ... Ci-dessous se trouvent les lignes de Penser en C ++ micc.unifi.it/bertini/download/programmazione/… Page 400 .. Veuillez vérifier .. S'il vous plaît upvote si vous êtes d'accord. Merci ..... Inlines inside classes Pour définir une fonction inline, vous devez normalement précéder la définition de fonction avec le mot clé inline. Cependant, cela n'est pas nécessaire dans une définition de classe. Toute fonction que vous définissez dans une définition de classe est automatiquement une fonction en ligne.
Saurabh Raoot le
Les auteurs de ce livre peuvent revendiquer ce qu'ils veulent, car ils écrivent des livres et non du code. C'est quelque chose que j'ai dû analyser en profondeur pour que mes démos 3D portables tiennent dans moins de 64 Ko en évitant autant que possible le code intégré. La programmation concerne les faits et non la religion, donc peu importe si un "programmeur divin" l'a dit dans un livre si cela ne représente pas ce qui se passe dans la pratique. Et la plupart des livres C ++ sont une collection de mauvais conseils, dans lesquels de temps en temps, vous pouvez trouver une astuce intéressante à ajouter à votre répertoire.
Pablo Ariel
Hey @PabloAriel Merci ... Veuillez analyser et laissez-moi savoir .. Je suis d'accord pour mettre à jour cette réponse selon l'analyse
Saurabh Raoot