Kiedy powinienem napisać słowo kluczowe inline
dla funkcji / metody w C ++?
Po wyświetleniu odpowiedzi na niektóre pytania:
Kiedy mam nie napisać słowa kluczowego „inline” dla funkcji / metody w C ++?
Kiedy kompilator nie będzie wiedział, kiedy ustawić funkcję / metodę jako „wbudowaną”?
Czy to ważne, czy aplikacja jest wielowątkowa, gdy pisze się „wbudowany” dla funkcji / metody?
c++
inline
one-definition-rule
Częściowy
źródło
źródło
inline
(9.3 / 2).Odpowiedzi:
O rany, jeden z moich domowych rękawów.
inline
jest bardziej niżstatic
lubextern
nakazujący kompilatorowi wstawienie twoich funkcji.extern
,static
,inline
Są dyrektywami podnośnik, używane niemal wyłącznie przez łącznik, a nie kompilator.Mówi się, że
inline
sugeruje to kompilatorowi, że według ciebie funkcja powinna być wbudowana. Być może było to prawdą w 1998 roku, ale dekadę później kompilator nie potrzebuje takich wskazówek. Nie wspominając, że ludzie zwykle mylą się, jeśli chodzi o optymalizację kodu, więc większość kompilatorów całkowicie ignoruje „podpowiedź”.static
- nazwy zmiennej / funkcji nie można używać w innych jednostkach tłumaczeniowych. Linker musi się upewnić, że przypadkowo nie używa statycznie zdefiniowanej zmiennej / funkcji z innej jednostki tłumaczeniowej.extern
- użyj tej zmiennej / nazwy funkcji w tej jednostce tłumaczeniowej, ale nie narzekaj, jeśli nie jest zdefiniowana. Linker to uporządkuje i upewni się, że cały kod, który próbował użyć jakiegoś symbolu zewnętrznego, ma swój adres.inline
- ta funkcja zostanie zdefiniowana w wielu jednostkach tłumaczeniowych, nie martw się o to. Linker musi upewnić się, że wszystkie jednostki tłumaczeniowe używają pojedynczego wystąpienia zmiennej / funkcji.Uwaga: Ogólnie rzecz biorąc, deklarowanie szablonów
inline
jest bezcelowe, ponieważ mają one już semantykę łączeniainline
. Jednak wyraźna specjalizacja i tworzenie szablonów wymagainline
do użycia.Konkretne odpowiedzi na twoje pytania:
Tylko wtedy, gdy chcesz, aby funkcja była zdefiniowana w nagłówku. Dokładniej tylko wtedy, gdy definicja funkcji może pojawiać się w wielu jednostkach tłumaczeniowych. Dobrym pomysłem jest zdefiniowanie małych (jak w jednej linijce) funkcji w pliku nagłówkowym, ponieważ daje to kompilatorowi więcej informacji do pracy przy optymalizacji kodu. Zwiększa także czas kompilacji.
Nie dodawaj inline tylko dlatego, że uważasz, że Twój kod będzie działał szybciej, jeśli kompilator go wstawi.
Ogólnie rzecz biorąc, kompilator będzie w stanie to zrobić lepiej niż ty. Jednak kompilator nie ma opcji wstawiania kodu, jeśli nie ma definicji funkcji. W maksymalnie zoptymalizowanym kodzie zazwyczaj wszystkie
private
metody są wprowadzane niezależnie od tego, czy o to poprosisz, czy nie.Na bok, aby zapobiec wstawianiu w GCC, użyj
__attribute__(( noinline ))
i w Visual Studio, użyj__declspec(noinline)
.Wielowątkowość nie wpływa w żaden sposób na wstawianie.
źródło
inline
słowo kluczowe nie są powiązane. Masz jednak dobry pomysł. Z reguły zgadywanie, co można by poprawić poprzez wstawianie, jest bardzo podatne na błędy. Wyjątkiem od tej reguły jest jeden liniowiec.Chciałbym przyczynić się do wszystkich wspaniałych odpowiedzi w tym wątku przekonującym przykładem, aby rozwiać wszelkie pozostałe nieporozumienia.
Biorąc pod uwagę dwa pliki źródłowe, takie jak:
inline111.cpp:
inline222.cpp:
Przypadek A:
Kompiluj :
Wyjście :
Dyskusja :
Nawet jeśli powinieneś mieć identyczne definicje funkcji wbudowanych, kompilator C ++ nie oflaguje go, jeśli tak nie jest (w rzeczywistości z powodu osobnej kompilacji nie ma możliwości sprawdzenia). Zapewnienie tego jest Twoim obowiązkiem!
Linker nie narzeka na regułę jednej definicji , jak
fun()
to zostało zadeklarowane jakoinline
. Ponieważ jednak inline111.cpp jest pierwszą jednostką translacyjną (która faktycznie wywołujefun()
) przetwarzaną przez kompilator, kompilator tworzy instancjęfun()
po pierwszym spotkaniu wywołania w inline111.cpp . Jeśli kompilator zdecyduje się nie rozszerzaćfun()
swojego wywołania z dowolnego miejsca w twoim programie ( np. Z inline222.cpp ), wywołanie dofun()
zawsze będzie połączone z instancją utworzoną z inline111.cpp (wywołanie dofun()
wewnątrz inline222.cppmoże również utworzyć wystąpienie w tej jednostce tłumaczeniowej, ale pozostanie niepowiązane). Rzeczywiście wynika to z identycznych&fun = 0x4029a0
wydruków.Wreszcie, pomimo
inline
sugestii kompilatora, aby faktycznie rozszerzyć jednowierszowąfun()
, całkowicie ignoruje twoją sugestię, co jest jasne, ponieważfun() = 111
w obu liniach.Przypadek B:
Kompiluj (zauważ odwrotną kolejność) :
Wyjście :
Dyskusja :
Przypadek ten potwierdza co zostały omówione w sprawie A .
Zwróć uwagę na ważny punkt, że jeśli skomentujesz rzeczywiste wywołanie
fun()
w inline222.cpp ( np. Całkowicie przestajesz komentowaćcout
w inline222.cpp ), to, pomimo kolejności kompilacji twoich jednostek tłumaczeniowych,fun()
zostanie utworzone natychmiast po pierwszym spotkaniu w inline111.cpp , czego wynikiem jest wydruk dla przypadku B asinline111: fun() = 111, &fun = 0x402980
.Sprawa C:
Kompilacja (powiadomienie -O2) :
lub
Wyjście :
Dyskusja :
-O2
optymalizacja zachęca kompilator do faktycznego rozszerzenia funkcji, które można wprowadzić (zauważ również, że-fno-inline
jest to domyślna opcja bez opcji optymalizacji). Jak widać tutaj na wydruku ,fun()
faktycznie został on wbudowany (zgodnie z jego definicją w tej konkretnej jednostce tłumaczeniowej), co skutkuje dwoma różnymifun()
wydrukami. Mimo to wciąż istnieje tylko jedna globalnie połączona instancjafun()
(zgodnie z wymogami standardu), co wynika z identycznego&fun
wydruku.źródło
inline
funkcje są niezdefiniowanym zachowaniem..cpp
czym każda z nich jest własną jednostką tłumaczeniową. Najlepiej dodaj przypadki-flto
włączonych / wyłączonych.Nadal musisz jawnie wstawić swoją funkcję podczas specjalizacji szablonu (jeśli specjalizacja znajduje się w pliku .h)
źródło
1) W dzisiejszych czasach prawie nigdy. Jeśli dobrym pomysłem jest wstawienie funkcji, kompilator zrobi to bez twojej pomocy.
2) Zawsze. Zobacz nr 1.
(Edytowane, aby odzwierciedlić, że podzieliłeś swoje pytanie na dwa pytania ...)
źródło
inline
jest nadal potrzebne, na przykład do zdefiniowania funkcji w pliku nagłówkowym (i jest to wymagane do wstawienia takiej funkcji w kilku jednostkach kompilacji).inline
specyfikator, jej instancje są automatycznie łączone w jedną przez linker i ODR nie jest używany.Jeśli funkcja jest zadeklarowana w nagłówku i zdefiniowane w
.cpp
pliku, należy nie napisać słowa kluczowego.Nie ma takiej sytuacji. Kompilator nie może ustawić funkcji w linii. Wszystko, co może zrobić, to wstawić niektóre lub wszystkie wywołania funkcji. Nie może tego zrobić, jeśli nie ma kodu funkcji (w takim przypadku linker musi to zrobić, jeśli jest w stanie to zrobić).
Nie, to wcale nie ma znaczenia.
źródło
Zależy to od zastosowanego kompilatora. Nie ślepo ufaj, że w dzisiejszych czasach kompilatory wiedzą lepiej niż ludzie, jak wstawiać i nigdy nie powinieneś używać go ze względu na wydajność, ponieważ jest to dyrektywa dotycząca łączenia, a nie wskazówka optymalizacyjna. Chociaż zgadzam się, że ideologicznie te argumenty są prawidłowe, spotkanie z rzeczywistością może być czymś innym.
Po przeczytaniu wielu wątków z ciekawości wypróbowałem efekty inline na kodzie, po prostu pracuję, a wyniki były takie, że uzyskałem mierzalne przyspieszenie dla GCC i brak przyspieszenia dla kompilatora Intela.
(Więcej szczegółów: symulacje matematyczne z kilkoma funkcjami krytycznymi zdefiniowanymi poza klasą, GCC 4.6.3 (g ++ -O3), ICC 13.1.0 (icpc -O3); dodanie inline do punktów krytycznych spowodowało + 6% przyspieszenia z kodem GCC).
Jeśli więc zakwalifikujesz GCC 4.6 jako nowoczesny kompilator, wynik będzie taki, że dyrektywa wbudowana nadal ma znaczenie, jeśli piszesz zadania wymagające dużej mocy obliczeniowej procesora i wiesz, gdzie dokładnie jest wąskie gardło.
źródło
W rzeczywistości prawie nigdy. Wszystko, co robisz, to sugerowanie, aby kompilator wstawił daną funkcję do siebie (np. Zastąp wszystkie wywołania tej funkcji / w jej treści). Oczywiście nie ma gwarancji: kompilator może zignorować dyrektywę.
Kompilator ogólnie dobrze sobie radzi z wykrywaniem i optymalizowaniem takich rzeczy.
źródło
inline
ma semantyczną różnicę w C ++ (np. W sposobie traktowania wielu definicji), co jest ważne w niektórych przypadkach (np. Szablony).Sprawdziłem to w Visual Studio 9 (15.00.30729.01), kompilując z / FAcs i patrząc na kod asemblera: Kompilator wygenerował wywołania funkcji członka bez włączonej optymalizacji w trybie debugowania . Nawet jeśli funkcja jest oznaczona __forceinline , nie jest generowany wbudowany kod środowiska wykonawczego.
źródło
Chcesz umieścić go na samym początku, przed typem zwrotu. Ale większość kompilatorów to ignoruje. Jeśli jest zdefiniowany i ma mniejszy blok kodu, większość kompilatorów i tak uważa go za wbudowany.
źródło
O ile nie piszesz biblioteki lub nie masz specjalnych powodów, możesz zapomnieć o optymalizacji łącza
inline
i użyć jej zamiast tego. Usuwa wymóg, że definicja funkcji musi znajdować się w nagłówku, aby można ją było uwzględnić podczas wstawiania między jednostkami kompilacji, co dokładnie pozwala.inline
(Ale patrz: Czy jest jakiś powód, dla którego nie należy korzystać z optymalizacji czasu łącza? )
źródło
Inline słowo kluczowe żąda od kompilatora zamiany wywołania funkcji na treść funkcji, najpierw ocenia wyrażenie, a następnie przekazuje. Zmniejsza to narzut wywołania funkcji, ponieważ nie ma potrzeby przechowywania adresu zwrotnego, a pamięć funkcji nie jest wymagana dla funkcji argumenty.
Kiedy użyć:
źródło
inline
C czy C ++. C Inline: stackoverflow.com/a/62287072/7194773 C ++ inline: stackoverflow.com/a/62230963/7194773C ++ Inline jest zupełnie inny niż C inline .
inline
sam wpływa na kompilator, asembler i linker. Jest to dyrektywa dla kompilatora, która mówi, że emituje tylko symbol tej funkcji / danych, jeśli jest on używany w jednostce tłumaczącej, a jeśli tak, to podobnie jak metody klasowe, każ asemblerowi, aby zapisał je w sekcji.section .text.c::function(),"axG",@progbits,c::function(),comdat
lub.section .bss.i,"awG",@nobits,i,comdat
danych.To następuje
.section name, "flags"MG, @type, entsize, GroupName[, linkage]
. Na przykład nazwa sekcji jest i nie jest używana, jest teraz widoczna dla asemblera, aw rezultacie linkera, ponieważ nie jest przechowywany w regularnym.text.c::function()
.axG
oznacza, że sekcja jest alokowalna, wykonywalna i w grupie, tj. zostanie podana nazwa grupy (i nie ma flagi M, więc nie zostanie podany żaden rozmiar); lub etc przez montera z powodu ich dyrektyw.@progbits
oznacza, że sekcja zawiera dane i nie jest pusta;c::function()
to nazwa grupy i grupa macomdat
powiązanie oznacza, że we wszystkich plikach obiektowych wszystkie sekcje napotkane z tą nazwą grupy oznaczone tagiem comdat zostaną usunięte z ostatecznego pliku wykonywalnego, z wyjątkiem 1, tzn. kompilator upewnia się, że w jednostce tłumaczącej jest tylko jedna definicja, a następnie mówi asemblerowi, aby wstawił we własnej grupie w pliku obiektowym (1 sekcja w 1 grupie), a następnie linker upewni się, że jeśli jakiś plik obiektowy ma grupę o tej samej nazwie, to umieści tylko jeden w końcowym .exe. Różnica pomiędzyinline
inline
.data
.text
static inline
w klasie oznacza, że jest to definicja typu, a nie deklaracja (pozwala zdefiniować element statyczny w klasie) i uczynić ją wbudowaną; teraz działa jak wyżej.static inline
w zakresie plików wpływa tylko na kompilator. Oznacza to, że dla kompilatora: emituje symbol tej funkcji / danych tylko wtedy, gdy jest używany w jednostce tłumaczącej i robi to jako zwykły symbol statyczny (przechowuj tekst. / Tekst bez dyrektywy .globl). Dla asemblera nie ma teraz różnicy międzystatic
istatic inline
extern inline
to deklaracja, która oznacza, że musisz zdefiniować ten symbol w jednostce tłumaczeniowej lub wyrzucić błąd kompilatora; jeśli jest zdefiniowany, traktuj go jako zwykły,inline
a dla asemblera i linkera nie będzie różnicy międzyextern inline
iinline
, więc jest to tylko ochrona kompilatora.Wszystko powyższe bez wiersza błędu zwija się do
inline int i[5]
. Oczywiście, jeśli tak,extern inline int i[] = {5};
toextern
zostaniesz zignorowany z powodu wyraźnej definicji poprzez przypisanie.inline
w przestrzeni nazw zobacz to i toźródło
Podczas opracowywania i debugowania kodu pomiń
inline
. Komplikuje to debugowanie.Głównym powodem ich dodania jest pomoc w optymalizacji wygenerowanego kodu. Zazwyczaj zamienia to zwiększoną przestrzeń kodu na szybkość, ale czasami
inline
oszczędza zarówno przestrzeń kodu, jak i czas wykonania.Przedyskutowanie tego rodzaju myśli na temat optymalizacji wydajności przed zakończeniem algorytmu jest przedwczesną optymalizacją .
źródło
inline
funkcje zwykle nie są wstawiane, chyba że są kompilowane z optymalizacjami, więc nie wpływają w żaden sposób na debugowanie. Pamiętaj, że to wskazówka, a nie żądanie.inline
funkcje były wbudowane. Niemożliwe było ustalenie w nich znaczącego punktu przerwania.inline
nic nie poprawi kodu w nowoczesnym kompilatorze, który może dowiedzieć się, czy wstawić, czy nie.Kiedy należy wstawić:
1.Jeśli chcemy uniknąć narzutu rzeczy, które wywoływane są podczas wywoływania funkcji, takich jak przekazywanie parametrów, przekazywanie kontroli, powrót kontroli itp.
2. Funkcja powinna być niewielka, często wywoływana, a wstawianie jest naprawdę korzystne, ponieważ zgodnie z zasadą 80-20, spróbuj ustawić te funkcje, które mają duży wpływ na wydajność programu.
Ponieważ wiemy, że inline to tylko prośba do kompilatora podobna do rejestracji i będzie kosztować cię w rozmiarze kodu obiektu.
źródło
inline
utraciło status wskazówki optymalizacyjnej, a większość kompilatorów używa go tylko do uwzględnienia wielu definicji - tak jak powinny IMO. Co więcej, od czasu C ++ 11register
jest całkowicie przestarzałe ze względu na wcześniejsze znaczenie „Wiem lepiej niż kompilator, jak zoptymalizować”: teraz jest to tylko słowo zastrzeżone, bez aktualnego znaczenia.inline
do pewnego stopnia słucha .Funkcja wbudowana w C ++ to potężna koncepcja, która jest powszechnie stosowana w klasach. Jeśli funkcja jest wbudowana, kompilator umieszcza kopię kodu tej funkcji w każdym punkcie, w którym funkcja jest wywoływana w czasie kompilacji.
Każda zmiana funkcji wbudowanej może wymagać ponownej kompilacji wszystkich klientów funkcji, ponieważ kompilator musiałby ponownie zastąpić cały kod, w przeciwnym razie kontynuowałby ze starą funkcjonalnością.
Aby wstawić funkcję, umieść słowo kluczowe inline przed nazwą funkcji i zdefiniuj funkcję przed wykonaniem jakichkolwiek wywołań funkcji. Kompilator może zignorować kwalifikator wbudowany, jeśli zdefiniowana funkcja jest większa niż linia.
Definicja funkcji w definicji klasy jest definicją funkcji wbudowanej, nawet bez użycia specyfikatora wbudowanego.
Poniżej znajduje się przykład, który wykorzystuje funkcję wstawiania do zwrócenia maksymalnie dwóch liczb
Aby uzyskać więcej informacji, zobacz tutaj .
źródło