znaczenie inline w interfejsach modułów

24

Rozważ plik nagłówkowy:

class T
{
private:
  int const ID;

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

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

lub alternatywnie:

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

W świecie przedmodułowym nagłówki te mogą być tekstowo zawarte w wielu jednostkach językowych bez naruszenia ODR. Ponadto, ponieważ zaangażowane funkcje składowe są stosunkowo małe, kompilator prawdopodobnie „wbuduje” (uniknie wywołań funkcji podczas korzystania) te funkcje, a nawet zoptymalizuje niektóre przypadki T.

W ostatnim raporcie ze spotkania, w którym C ++ 20 został zakończony, mogłem przeczytać następujące oświadczenie:

Wyjaśniliśmy znaczenie inlineinterfejsów modułów: chodzi o to, że ciała funkcji, które nie są wyraźnie zadeklarowane, inlinenie są częścią ABI modułu, nawet jeśli te ciała funkcji pojawiają się w interfejsie modułu. Aby zapewnić autorom modułów większą kontrolę nad ich ABI, funkcje składowe zdefiniowane w obiektach klas w interfejsach modułów nie są już domyślnie inline.

Nie jestem pewien, że się nie mylę. Czy to oznacza, że ​​w świecie modułów, aby kompilator mógł zoptymalizować wywołania funkcji, musimy je opatrzyć adnotacjami, inlinenawet jeśli są zdefiniowane w klasie?

Jeśli tak, to czy następujący interfejs modułu byłby równoważny z powyższymi nagłówkami?

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

Mimo że nadal nie mam kompilatora ze wsparciem dla modułów, chciałbym zacząć używać go w inlinerazie potrzeby, aby zminimalizować przyszłe refaktoryzacje.

metalfox
źródło

Odpowiedzi:

11

Czy to oznacza, że ​​w świecie modułów, aby kompilator mógł zoptymalizować wywołania funkcji, musimy je opatrzyć adnotacjami, inlinenawet jeśli są zdefiniowane w klasie?

W pewnym stopniu.

Inlining to optymalizacja „jak gdyby”, a inlinizacja może zachodzić nawet między jednostkami tłumaczeniowymi, jeśli kompilator jest wystarczająco sprytny.

Biorąc to pod uwagę, wstawianie jest najłatwiejsze w przypadku pracy w ramach jednego działu tłumaczeń. Dlatego, aby promować łatwe inlinewstawianie, funkcja zadeklarowana musi mieć swoją definicję w dowolnej jednostce tłumaczeniowej, w której jest używana. Nie oznacza to, że kompilator na pewno go wstawi (lub na pewno nie wstawi żadnej inlineniewykwalifikowanej funkcji), ale znacznie ułatwia proces wstawiania, ponieważ wstawianie odbywa się w jednostce TU, a nie między nimi.

Definicje elementów klasy zdefiniowane w klasie w świecie przedmodułowym są deklarowane inlinedomyślnie. Dlaczego? Ponieważ definicja należy do klasy. W świecie przedmodułowym definicje klas, które są wspólne dla JT, są udostępniane przez włączenie tekstowe. Członkowie zdefiniowani w klasie byliby zatem zdefiniowani w nagłówku wspólnym dla tych JT. Jeśli więc wiele JT korzysta z tej samej klasy, wiele JJ robi to poprzez włączenie definicji klasy i definicji jej elementów zadeklarowanych w nagłówku.

Oznacza to, że jesteś w tym definicji i tak , więc dlaczego nie zrobić go inline?

Oczywiście oznacza to, że definicja funkcji jest teraz częścią tekstu klasy. Zmiana definicji elementu zadeklarowanego w nagłówku wymusza rekurencyjną rekompilację każdego pliku zawierającego ten nagłówek. Nawet jeśli sam interfejs klasy się nie zmienia, nadal musisz dokonać ponownej kompilacji. Tak więc tworzenie takich funkcji domyślnie inlinenie zmienia tego, więc równie dobrze możesz to zrobić.

Aby tego uniknąć w świecie przedmodułowym, możesz po prostu zdefiniować element członkowski w pliku C ++, który nie zostanie uwzględniony w innych plikach. Tracisz łatwe wprowadzanie, ale zyskujesz czas kompilacji.

Ale o to chodzi: jest to artefakt polegający na wykorzystaniu włączenia tekstu jako sposobu na dostarczenie klasy do wielu miejsc.

W świecie modułowym prawdopodobnie chciałbyś zdefiniować każdą funkcję składową w samej klasie, jak widzimy w innych językach, takich jak Java, C #, Python i tym podobne. Utrzymuje to rozsądną lokalizację kodu i zapobiega konieczności ponownego wpisywania tej samej sygnatury funkcji, spełniając w ten sposób potrzeby DRY.

Ale jeśli wszyscy członkowie są zdefiniowani w ramach definicji klasy, to zgodnie ze starymi regułami wszyscy ci członkowie byliby inline. Aby moduł mógł być funkcją inline, artefakt modułu binarnego musiałby zawierać definicję tych funkcji. Co oznacza, że ​​za każdym razem, gdy zmienisz choćby jeden wiersz kodu w takiej definicji funkcji, moduł będzie musiał zostać zbudowany wraz z każdym modułem zależnie od niego rekurencyjnie.

Usunięcie niejawnych inlinemodułów daje użytkownikom takie same uprawnienia, jakie mieli w dniach włączenia tekstu, bez konieczności przenoszenia definicji poza klasę. Możesz wybrać, które definicje funkcji są częścią modułu, a które nie.

Nicol Bolas
źródło
8

Pochodzi z P1779 , właśnie przyjętej w Pradze kilka dni temu. Z wniosku:

W tym artykule zaproponowano usunięcie niejawnego statusu wbudowanego z funkcji zdefiniowanych w definicji klasy dołączonej do (nazwanego) modułu. Dzięki temu klasy mogą czerpać korzyści z unikania zbędnych deklaracji, zachowując elastyczność oferowaną autorom modułów w zakresie deklarowania funkcji z wbudowanym lub bez. Co więcej, pozwala wstrzykiwanym znajomym szablonów klas (których nie można ogólnie zdefiniować poza definicją klasy) w ogóle nie są wbudowane. To także rozwiązuje komentarz NB US90 .

Artykuł (między innymi) usunął zdanie:

Funkcja zdefiniowana w definicji klasy jest funkcją wbudowaną.

i dodał zdanie:

W module globalnym funkcja zdefiniowana w definicji klasy jest domyślnie wbudowana ([class.mfct], [class.friend]).


Twój przykład export module Mto modułowy odpowiednik programu początkowego. Zauważ, że kompilatory wykonują już funkcje wbudowane, które nie są opatrzone adnotacjami inline, po prostu dodatkowo wykorzystują obecność inlinesłowa kluczowego w swojej heurystyce.

Barry
źródło
Tak więc funkcja w module bez inlinesłowa kluczowego nigdy nie zostanie wprowadzona przez kompilator, prawda?
metalfox
1
@metalfox Nie, nie uważam, że to prawda.
Barry
1
Widzę. Dzięki. To tak, jak zostało zdefiniowane w pliku cpp, co niekoniecznie oznacza, że ​​nie będzie wstawiane w czasie łącza.
metalfox