Dlaczego funkcje inline C ++ znajdują się w nagłówku?

120

Uwaga: nie chodzi o to, jak używać funkcji inline ani jak one działają, a raczej o to, dlaczego są robione tak, jak są.

Deklaracja funkcji składowej klasy nie musi definiować funkcji inline, ponieważ jest to tylko faktyczna implementacja funkcji. Na przykład w pliku nagłówkowym:

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

Dlaczego więc wbudowana implementacja funkcji klas musi znajdować się w pliku nagłówkowym? Dlaczego nie mogę umieścić funkcji wbudowanej w .cpppliku? Gdybym próbował umieścić definicję wbudowaną w .cpppliku, wystąpiłby błąd w następujący sposób:

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
źródło
@Charles Powiedziałbym, że drugi link jest podobny, ale pytam więcej o logikę stojącą za tym, dlaczego inline działa tak, jak działa.
thecoshman,
2
W takim przypadku myślę, że mogłeś źle zrozumieć „pliki wbudowane” lub „pliki nagłówkowe”; żadne z twoich twierdzeń nie jest prawdziwe. Możesz mieć wbudowaną implementację funkcji składowej i możesz umieścić definicje funkcji wbudowanych w pliku nagłówkowym, po prostu może to nie być dobry pomysł. Czy możesz wyjaśnić swoje pytanie?
CB Bailey,
Po edycji, myślę, że możesz pytać o sytuacje, w których inlinepojawia się w definicji, ale nie we wcześniejszej deklaracji, a odwrotnie . Jeśli tak, to może pomóc: stackoverflow.com/questions/4924912/…
CB Bailey,

Odpowiedzi:

122

Definicja inlinefunkcji nie musi znajdować się w pliku nagłówkowym, ale ze względu na regułę jednej definicji ( ODR ) dla funkcji wbudowanych, identyczna definicja funkcji musi istnieć w każdej jednostce tłumaczeniowej, która jej używa.

Najłatwiejszym sposobem osiągnięcia tego jest umieszczenie definicji w pliku nagłówkowym.

Jeśli chcesz umieścić definicję funkcji w jednym pliku źródłowym, nie powinieneś jej deklarować inline. Nie zadeklarowana funkcja nie inlineoznacza, że ​​kompilator nie może wbudować funkcji.

To, czy powinieneś zadeklarować funkcję, inlineczy nie, jest zwykle wyborem, którego powinieneś dokonać w oparciu o wersję jednej definicji reguł, która jest dla ciebie najbardziej sensowna; dodawanie, inlinea następnie ograniczanie przez kolejne ograniczenia nie ma sensu.

CB Bailey
źródło
Ale kompilator nie kompiluje pliku .cpp, który zawiera pliki .h ... tak więc kiedy kompiluje plik .cpp, ma zarówno opóźnienie, jak i pliki źródłowe. Inne pliki nagłówkowe wciągnięta to tylko ich więc kompilator „Trust”, że te funkcje istnieją i będą realizowane w innym pliku źródłowego
thecoshman
1
Właściwie to o wiele lepsza odpowiedź niż moja, +1ode mnie!
sbi
2
@thecoshman: Istnieją dwie różnice. Plik źródłowy a plik nagłówkowy. Zgodnie z konwencją, plik nagłówkowy zwykle odnosi się do pliku źródłowego, który nie jest podstawą jednostki tłumaczeniowej, ale jest # uwzględniony tylko w innych plikach źródłowych. Następnie jest deklaracja a definicja. Możesz mieć deklaracje lub definicje funkcji w plikach nagłówkowych lub „normalnych” plikach źródłowych. Obawiam się, że nie jestem pewien, o co pytasz w swoim komentarzu.
CB Bailey,
nie martw się, rozumiem, dlaczego tak jest teraz ... chociaż nie jestem pewien, kto naprawdę odpowiedział na to pytanie. Połączenie twojej i odpowiedzi @ Xanatos wyjaśniło mi to.
thecoshman,
113

Można na to spojrzeć na dwa sposoby:

  1. Funkcje wbudowane są zdefiniowane w nagłówku, ponieważ aby wbudować wywołanie funkcji, kompilator musi mieć możliwość zobaczenia treści funkcji. Aby naiwny kompilator to zrobił, treść funkcji musi znajdować się w tej samej jednostce tłumaczenia, co wywołanie. (Nowoczesny kompilator może optymalizować w jednostkach tłumaczeniowych, więc wywołanie funkcji może być wbudowane, nawet jeśli definicja funkcji znajduje się w oddzielnej jednostce tłumaczeniowej, ale te optymalizacje są drogie, nie zawsze są włączone i nie zawsze były obsługiwane przez kompilator)

  2. funkcje zdefiniowane w nagłówku muszą być zaznaczone, inlineponieważ w przeciwnym razie każda jednostka translacji, która zawiera nagłówek, będzie zawierała definicję funkcji, a linker będzie narzekał na wiele definicji (naruszenie reguły jednej definicji). inlineKluczowe tłumi tego, pozwalając wielu jednostek tłumaczenie zawierać (identyczne) definicje.

Te dwa wyjaśnienia tak naprawdę sprowadzają się do tego, że inlinesłowo kluczowe nie robi dokładnie tego, czego można się spodziewać.

Kompilator C ++ może zastosować optymalizację wbudowaną (zastąpić wywołanie funkcji ciałem wywoływanej funkcji, oszczędzając narzut wywołania) w dowolnym momencie, o ile nie zmienia obserwowalnego zachowania programu.

Słowo inlinekluczowe ułatwia kompilatorowi zastosowanie tej optymalizacji, umożliwiając wyświetlanie definicji funkcji w wielu jednostkach tłumaczeniowych, ale użycie słowa kluczowego nie oznacza, że ​​kompilator musi wbudować funkcję, a nie użycie słowa kluczowego nie. Zabroń kompilatorowi wstawiania funkcji.

jalf
źródło
23

To jest ograniczenie kompilatora C ++. Jeśli umieścisz funkcję w nagłówku, wszystkie pliki cpp, w których można ją wstawić, będą widzieć „źródło” Twojej funkcji, a kompilator będzie mógł wykonać wstawianie. W przeciwnym razie wstawianie musiało być wykonane przez konsolidator (każdy plik cpp jest kompilowany osobno w pliku obj). Problem w tym, że byłoby to dużo trudniejsze do zrobienia w linkerze. Podobny problem występuje w przypadku klas / funkcji „szablonów”. Muszą zostać utworzone przez kompilator, ponieważ konsolidator miałby problem z ich utworzeniem (utworzeniem specjalistycznej wersji). Niektóre nowsze kompilatory / konsolidatory mogą wykonywać kompilację / linkowanie „dwuprzebiegowe”, gdzie kompilator wykonuje pierwszy przebieg, a następnie konsolidator wykonuje swoją pracę i wywołuje kompilator w celu rozwiązania nierozwiązanych rzeczy (inline / templates ...)

xanatos
źródło
Rozumiem! tak, to nie dla klasy, która sama używa funkcji wbudowanej, jej innego kodu, który korzysta z funkcji wbudowanych. Widzą tylko plik nagłówkowy klasy, która jest wstawiana!
thecoshman
11
Nie zgadzam się z tą odpowiedzią, to nie jest limit kompilatora C ++; jest to wyłącznie sposób określania reguł językowych. Reguły językowe pozwalają na prosty model kompilacji, ale nie zabraniają alternatywnych implementacji.
CB Bailey,
3
Zgadzam się z @Charles. W rzeczywistości istnieją kompilatory, które są wbudowane w jednostki tłumaczeniowe, więc zdecydowanie nie jest to spowodowane ograniczeniami kompilatora.
sbi
5
Chociaż wydaje się, że ta odpowiedź zawiera błędy techniczne, pomogła mi zobaczyć, jak kompilator działa z plikami nagłówkowymi i tym podobnymi.
thecoshman,
10

Powodem jest to, że kompilator musi faktycznie zobaczyć definicję , aby móc ją upuścić w miejsce wywołania.

Pamiętaj, że C i C ++ używają bardzo uproszczonego modelu kompilacji, w którym kompilator zawsze widzi tylko jedną jednostkę tłumaczeniową na raz. (Nie udaje się to w przypadku eksportu, co jest głównym powodem, dla którego tylko jeden dostawca faktycznie go wdrożył).

sbi
źródło
9

Słowo inlinekluczowe c ++ jest mylące, nie oznacza „wbudowanej tej funkcji”. Jeśli funkcja jest zdefiniowana jako inline, oznacza to po prostu, że można ją zdefiniować wiele razy, o ile wszystkie definicje są równe. Funkcja oznaczona inlinejako prawdziwa funkcja, która jest wywoływana zamiast pobierania kodu w miejscu, w którym jest wywoływana, jest całkowicie legalna .

Definiowanie funkcji w pliku nagłówkowym jest potrzebne w przypadku szablonów, ponieważ np. Klasa z szablonem nie jest tak naprawdę klasą, jest szablonem dla klasy, której można dokonywać w wielu odmianach. Aby kompilator mógł np. UtworzyćFoo<int>::bar() funkcję, gdy używasz szablonu Foo do tworzenia klasy Foo , rzeczywista definicja Foo<T>::bar()musi być widoczna.

Erik
źródło
A ponieważ jest to szablon dla klasy , nie jest nazywany klasą szablonu , ale szablonem klasy .
sbi
4
Pierwszy akapit jest całkowicie poprawny (i chciałbym podkreślić „mylące”), ale nie widzę potrzeby umieszczania non sequitur w szablonach.
Thomas Edleson,
Niektóre kompilatory wykorzystają to jako wskazówkę, że funkcja prawdopodobnie może być wbudowana, ale w rzeczywistości nie ma gwarancji, że zostanie ona wstawiona tylko dlatego, że ją zadeklarujesz inline(ani nie zadeklarowanie jej inlinegwarantuje, że nie zostanie ona wstawiona).
Keith M
4

Wiem, że to stary wątek, ale pomyślałem, że powinienem wspomnieć o tym externsłowie kluczowym. Niedawno napotkałem ten problem i rozwiązałem go w następujący sposób

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());
        }
    }
}
płomień000
źródło
6
Zwykle nie spowoduje to rzeczywistego wstawienia funkcji, chyba że używasz optymalizacji całego programu (WPO).
Chuck Walbourn
3

Ponieważ kompilator musi je zobaczyć, aby je wstawić . Pliki nagłówkowe to „komponenty”, które są zwykle zawarte w innych jednostkach tłumaczeniowych.

#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
źródło
1

Funkcje wbudowane

W C ++ makro to nic innego jak funkcja inline. Więc teraz makra są pod kontrolą kompilatora.

  • Ważne : Jeśli zdefiniujemy funkcję wewnątrz klasy, stanie się ona automatycznie Inline

Kod funkcji Inline jest zastępowany w miejscu jej wywołania, co zmniejsza narzut wywołania funkcji.

W niektórych przypadkach inlining funkcji nie może działać, na przykład

  • Jeśli zmienna statyczna jest używana wewnątrz funkcji inline.

  • Jeśli funkcja jest skomplikowana.

  • Jeśli rekurencyjne wywołanie funkcji

  • Jeśli adres funkcji przyjęty niejawnie lub jawnie

Funkcja zdefiniowana poza klasą, jak poniżej, może stać się wbudowana

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

Funkcje zdefiniowane w klasie również stają się wbudowane

// 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();
} 

Tutaj obie funkcje getSpeed ​​i setSpeed ​​staną się wbudowane

Saurabh Raoot
źródło
Ech, może trochę fajnych informacji, ale tak naprawdę nie próbuje wyjaśnić dlaczego . Może tak, ale po prostu tego nie wyjaśniasz.
thecoshman
2
Następujące stwierdzenie nie jest prawdziwe: „Ważne: jeśli zdefiniujemy funkcję wewnątrz klasy, stanie się ona automatycznie Inline”. Nawet jeśli wpiszesz „inline” w deklaracji / definicji, możesz być pewien, że faktycznie jest ona wstawiana. Nawet w przypadku szablonów. Może chodziło Ci o to, że kompilator automatycznie przyjmuje słowo kluczowe „inline”, ale nie musi go stosować. Zauważyłem, że w większości przypadków nie zawiera on takich definicji w nagłówku, nawet w przypadku prostych funkcji constexpr z podstawowe arytmetyka.
Pablo Ariel
Hej, dzięki za komentarze ... Poniżej znajdują się wiersze z Thinking in C ++ micc.unifi.it/bertini/download/programmazione/ ... Strona 400 .. Proszę sprawdzić .. Proszę zagłosować za zgodą. Dzięki ..... Inline wewnątrz klas Aby zdefiniować funkcję wbudowaną, musisz zwykle poprzedzić definicję funkcji słowem kluczowym inline. Jednak nie jest to konieczne w definicji klasy. Każda funkcja zdefiniowana w definicji klasy jest automatycznie wstawiana.
Saurabh Raoot
Autorzy tej książki mogą żądać tego, czego chcą, ponieważ piszą książki, a nie kodują. To jest coś, co musiałem dogłębnie przeanalizować, aby moje przenośne dema 3D zmieściły się w mniej niż 64 kb, unikając w jak największym stopniu wbudowanego kodu. Programowanie dotyczy faktów, a nie religii, więc nie ma znaczenia, czy jakiś „bogaty programista” powiedział to w książce, jeśli nie przedstawia tego, co dzieje się w praktyce. Większość książek o C ++ zawiera zbiór złych rad, w których od czasu do czasu można znaleźć fajną sztuczkę do dodania do swojego repertuaru.
Pablo Ariel
Hej @PabloAriel Dzięki ... Przeanalizuj i daj mi znać ... Mogę zaktualizować tę odpowiedź zgodnie z analizą
Saurabh Raoot