Pytanie Funkcje składowe inline w C ++


ISO C ++ mówi, że wbudowana definicja funkcji składowej w C ++ jest taka sama, jak deklarowanie jej inline. Oznacza to, że funkcja zostanie zdefiniowana w każdej jednostce kompilacji, której użyto funkcji składowej. Jednakże, jeśli wywołanie funkcji nie może być wstawione z jakiegokolwiek powodu, funkcja ma być instancja "jak zwykle". (http://msdn.microsoft.com/en-us/library/z8y1yy88%28VS.71%29.aspx) Problem z tą definicją polega na tym, że nie określa, w której jednostce tłumaczeniowej powstanie. Napotkany problem polega na tym, że gdy napotykam dwa pliki obiektów w pojedynczej bibliotece statycznej, z których obie mają odniesienie do pewnej funkcji wstawianego elementu, która nie może być wstawiona, łącznik może "wybrać" dowolny plik obiektowy jako źródło dla definicji. Ten konkretny wybór może wprowadzić niepotrzebne zależności. (między innymi)

Na przykład: W bibliotece statycznej

A.h:

class A{
  public:
    virtual bool foo() { return true; }
};

U1.cpp:

A a1;

U2.cpp:

A a2;

i wiele zależności

W innym projekcie main.cpp:

#include "A.h"

int main(){
  A a;
  a.foo();
  return 0;
}

Drugi projekt odnosi się do pierwszego. Skąd mam wiedzieć, jakiej definicji użyje kompilator, a co za tym idzie, które pliki obiektów wraz z ich zależnościami będą połączone? Czy jest coś, co standard mówi w tej sprawie? (Próbowałem, ale nie udało mi się tego znaleźć)

Dzięki

Edycja: ponieważ widziałem, że niektórzy ludzie źle rozumieją pytanie, chciałbym podkreślić: Jeśli kompilator zdecydował się utworzyć symbol dla tej funkcji (iw tym przypadku będzie, z powodu "wirtualności", pojawi się kilka (zewnętrznie widzianych) wystąpień w różnych plikach obiektów, z jaką definicją (z którego pliku obiektowego?) czy linker wybierze?)


14
2018-03-02 18:16


pochodzenie


Definicja czego? Dlaczego mamy tam a1 i a2? Masz tylko jedną definicję klasy A. - Mykola Golubyev
Definicja A :: foo, ponieważ nie może być inline (jako wirtualny, musi mieć przynajmniej zewnętrzną definicję) - EFraim
Nie z powodu "wirtualności"; to ze względu na "intuicyjność" istnieje wiele definicji. - Rob Kennedy
Właściwie mówię o wystąpieniach. (jak w kropelce kodu binarnego z symbolem widocznym dla łącznika) Przepraszamy za zamieszanie. - EFraim


Odpowiedzi:


Kiedy masz wbudowaną metodę, która jest zmuszana do nieinskrybowania przez kompilator, to naprawdę stworzy instancję w każdej skompilowanej jednostce, która jej używa. Obecnie większość kompilatorów jest wystarczająco inteligentna, aby utworzyć instancję tylko w razie potrzeby (jeśli jest używana), więc samo uwzględnienie pliku nagłówkowego nie wymusi instancji. Linker, jak powiedziałeś, wybierze jedną z instancji do włączenia do pliku wykonywalnego - pamiętaj jednak, że rekord wewnątrz modułu obiektowego jest szczególnego rodzaju (na przykład COMDEF), aby dać linkerowi wystarczającą ilość informacje, aby wiedzieć, jak odrzucić zduplikowane wystąpienia. Zapisy te nie będą więc skutkować niepożądanymi zależnościami między modułami, ponieważ łącznik będzie ich używał z mniejszym priorytetem niż "zwykłe" rekordy w celu rozwiązania zależności.

W podanym przykładzie, naprawdę nie wiesz, ale to nie ma znaczenia. Łącznik nie rozwiąże zależności na podstawie samych nieinstruowanych instancji. Wynik (pod względem modułów zawartych przez linker) będzie tak dobry, jak gdyby metoda inline nie istniała.


7
2018-03-02 19:27



Dziękuję za odpowiedź. W moim konkretnym przypadku ma to znaczenie, tak jakby kompilator (MSVC 2008) dotknął konkretnego pliku obiektu, aby uzyskać z niego definicję, będzie musiał także rozwiązać wszystkie "odniesienia" tego pliku obiektu, prawdopodobnie wymagając znacznie więcej zależności niż potrzebne. - EFraim
Rozumiem ... Cóż, w tym przypadku proponuję przeniesienie jak największej liczby plików obiektów z głównego projektu do (jednej lub więcej) bibliotek statycznych. Pliki znajdujące się w bibliotekach statycznych mają zagwarantowane połączenie tylko w razie potrzeby. - Fabio Ceconello


Tylko moje dwa centy. Nie dotyczy to w szczególności funkcji wirtualnej, ale ogólnie funkcji inline i member. Może to jest przydatne.

C ++

Jeśli chodzi o standard C ++, funkcja inline musi być zdefiniowane w każdej jednostce tłumaczeniowej, w której jest używana. A niestatyczna funkcja inline będzie miała te same zmienne statyczne w każdej jednostce tłumaczeniowej i tym samym adresie. Kompilator / linker będzie musiał połączyć wiele definicji w jedną funkcję, aby to osiągnąć. Dlatego zawsze umieszczaj definicję funkcji wstawianej w nagłówku - lub nie umieszczaj jej w nagłówku, jeśli zdefiniujesz ją tylko w pliku implementacji (".cpp") (dla funkcji nie należącej do członka), ponieważ jeśli Gdyby ktoś go użył, otrzymasz błąd linkera dotyczący niezdefiniowanej funkcji lub coś podobnego.

Różni się to od funkcji nieliniowych, które muszą być zdefiniowane tylko raz w całym programie (zasada jednej definicji). W przypadku funkcji inline wiele definicji, jak opisano powyżej, jest raczej normalnym przypadkiem. I to jest niezależne od tego, czy połączenie jest zwyczajnie podane, czy też nie. Reguły dotyczące funkcji wbudowanych wciąż mają znaczenie. Niezależnie od tego, czy kompilator Microsoft przestrzega tych zasad, czy nie, nie mogę ci powiedzieć. Jeśli będzie to zgodne ze Standardem w tym zakresie, to tak będzie. Mogę sobie jednak wyobrazić, że kombinacja z wykorzystaniem wirtualnych bibliotek dll i różnych jednostek tłumaczeniowych może być problematyczna. Nigdy tego nie testowałem, ale uważam, że nie ma problemów.

W przypadku funkcji składowych, jeśli zdefiniujesz swoją funkcję w klasie, jest ona niejawnie wbudowana. A ponieważ pojawia się w nagłówku, zasada, że ​​musi być zdefiniowana w każdej jednostce tłumaczeniowej, w której jest używana, jest automatycznie spełniana. Jeśli jednak zdefiniujesz funkcję poza klasą i plik nagłówkowy (na przykład, ponieważ istnieje zależność cykliczna z kodem pośrednim), to ta definicja musi być wstawiona, jeśli dołączasz odpowiedni plik więcej niż raz, do unikaj błędów wielu definicji zgłaszanych przez linker. Przykład pliku f.h:

struct f {
    // inline required here or before the definition below
    inline void g();
};

void f::g() { ... }

Miałoby to taki sam skutek, jak umieszczenie definicji bezpośrednio w definicji klasy.

C99

Zauważ, że reguły dotyczące funkcji wbudowanych są bardziej skomplikowane dla C99 niż dla C ++. Tutaj funkcja inline może być zdefiniowana jako definicja śródliniowa, z których może istnieć więcej niż jeden w całym programie. Ale jeśli taka definicja (inline) jest używana (np. Jeśli jest wywołana), to tam musi być również dokładnie jeden zewnętrzna definicja w całym programie zawarta w innej jednostce tłumaczeniowej. Uzasadnienie tego (cytując z pliku PDF wyjaśniającego uzasadnienie kilku funkcji C99):

Inlining w C99 rozszerza specyfikację C ++ na dwa sposoby. Po pierwsze, jeśli funkcja jest deklarowana inline w jednej jednostce tłumaczeniowej, nie musi być deklarowana inline w każdej innej jednostce tłumaczeniowej. Pozwala to na przykład na funkcję biblioteczną, która ma być wstawiona w obrębie biblioteki, ale dostępna tylko poprzez zewnętrzną definicję w innym miejscu. Alternatywa użycia funkcji otoki dla funkcji zewnętrznej wymaga dodatkowej nazwy; i może również negatywnie wpływać na wydajność, jeśli tłumacz nie w rzeczywistości dokonuje podstawiania.

Po drugie, wymóg, że wszystkie definicje funkcji wstawianej są "dokładnie takie same", zastępuje się wymogiem, że zachowanie programu nie powinno zależeć od tego, czy wywołanie jest realizowane z widoczną definicją wstawianą, czy zewnętrzną definicją funkcjonować. Umożliwia to wyspecjalizowaną definicję w celu wyspecjalizowania jej użycia w określonej jednostce tłumaczeniowej. Na przykład zewnętrzna definicja funkcji biblioteki może zawierać pewne sprawdzanie poprawności argumentów, które nie jest potrzebne w przypadku wywołań wykonanych z innych funkcji w tej samej bibliotece. Rozszerzenia te oferują pewne zalety; a programiści obawiający się zgodności mogą po prostu przestrzegać bardziej surowych zasad C ++.

Dlaczego włączam tutaj C99? Ponieważ wiem, że kompilator Microsoft obsługuje pewne rzeczy z C99. Tak więc na tych stronach MSDN niektóre rzeczy mogą pochodzić także z C99 - nie wymyśliłem jednak niczego szczególnego. Należy zachować ostrożność podczas czytania i stosowania tych technik do własnego kodu C ++, który ma być przenośnym C ++. Prawdopodobnie informuje, które części są specyficzne dla C99, a które nie.

Dobrym miejscem do testowania małych fragmentów C ++ dla standardowej zgodności jest kompilator online comeau. Jeśli zostanie odrzucony, można być pewnym, że nie jest to zgodne ze standardem.


8
2018-03-02 18:35





AFAIK, nie ma standardowej definicji jak i kiedy kompilator C ++ wbuduje wywołanie funkcji. Są to zazwyczaj "zalecenia", których kompilator nie jest w żaden sposób wymagany. W rzeczywistości różni użytkownicy mogą chcieć różnych zachowań. Jeden użytkownik może dbać o szybkość, podczas gdy inny może dbać o mały wygenerowany rozmiar pliku obiektu. Ponadto kompilatory i platformy są różne. Niektóre kompilatory mogą stosować inteligentniejszą analizę, inne nie. Niektóre kompilatory mogą generować dłuższy kod z linii lub pracować na platformie, na której połączenia są zbyt drogie itp.

Gdy masz funkcję inline, kompilator powinien nadal generować symbol i ostatecznie rozwiązać jedną jego wersję. Tak więc, jeśli znajduje się w statycznej bibliotece, ludzie nadal mogą wywoływać tę funkcję nie w linii. Innymi słowy, nadal działa jak normalna funkcja.

Jedynym skutkiem inline jest to, że w niektórych przypadkach kompilator zobaczy wywołanie, zobacz wbudowane i całkowicie odrzuci połączenie, ale funkcja powinna nadal tam być, po prostu nie zostanie wywołana w tym przypadku.


4
2018-03-02 18:27





Jeśli kompilator zdecydował się utworzyć symbol dla tej funkcji (iw tym przypadku będzie, z powodu "wirtualności", pojawi się kilka (zewnętrznie widzianych) wystąpień w różnych plikach obiektów, z jaką definicją (z którego pliku obiektowego?) czy linker wybierze?)

Definicja, która jest obecna w odpowiedniej jednostce tłumaczeniowej. I jednostka tłumaczeniowa nie może, i powtarzam, nie może mieć dokładnie jednej takiej definicji. Standard jest o tym jasny.

[...] linker może "wybrać" dowolny plik obiektowy jako źródło definicji.

EDYTOWAĆ: Aby uniknąć dalszych nieporozumień, pozwólcie, że wyjaśnię: zgodnie z moim odczytaniem normy, możliwość posiadania wielu definicji w różnych JT nie daje nam każdy praktyczna dźwignia. Z praktycznego punktu widzenia mam na myśli nawet nieco różne implementacje. Teraz, jeśli wszystkie twoje JT mają dokładnie taką samą definicję, po co zawracać sobie głowę, z której TU odbierana jest definicja?

Jeśli przejrzysz standard, zobaczysz, że zasada One Definition jest stosowana wszędzie. Chociaż to jest dozwolone posiadanie wielu definicji inline funkcjonować:

3.2 Jedna zasada definicji:

5 Może istnieć więcej niż jedna definicja typu klasy (Klauzula 9), koncepcja (14.9), mapa idei (14.9.2), typ wyliczenia (7.2), funkcja inline z łączem zewnętrznym (7.1.2), [.. .]

Przeczytaj to w połączeniu z

3 [...] Funkcję inline należy zdefiniować w każdej jednostce tłumaczeniowej, w której jest używana.

Oznacza to, że funkcja zostanie zdefiniowana w każdej jednostce kompilacji [...]

i

7.1.2 Specyfikatory funkcji

2 Deklaracja funkcji (8.3.5, 9.3, 11.4) z wbudowanym specyfikatorem deklaruje funkcję inline. Specyfikator śródliniowy wskazuje na implementację, że podstawiane zastępowanie treści funkcji w miejscu połączenia ma być preferowane w stosunku do zwykłego mechanizmu wywoływania funkcji. Implementacja nie jest wymagana do wykonania tego śródliniowego zastąpienia w punkcie połączenia; jednak, nawet jeśli pominięto tę podstawową zamianę, pozostałe zasady   dla funkcji inline określonych w 7.1.2 będzie nadal przestrzegana.

3 Funkcja zdefiniowana w definicji klasy jest funkcją inline. Specyfikator śródliniowy nie pojawia się w deklaracji funkcji zakresu blokowego. [Przypis: 82] Jeżeli specyfikator śródliniowy jest używany w deklaracji przyjaciela, deklaracja ta musi być definicją lub funkcja powinna być wcześniej zadeklarowana jako wbudowana.

i przypis:

82) Słowo kluczowe wbudowane nie ma wpływu na powiązanie funkcji.   § 7.1.2 138

jak również:

4 Funkcja liniowa musi być zdefiniowana w każdej jednostce tłumaczeniowej, w której jest używana, iw każdym przypadku powinna mieć dokładnie taką samą definicję (3.2). [Uwaga: można wywołać wywołanie funkcji inline, zanim jego definicja pojawi się w jednostce tłumaczeniowej. -End note] Jeśli definicja funkcji pojawia się w jednostce tłumaczeniowej przed pierwszą deklaracją jako inline, program jest źle sformułowany. Jeśli funkcja ma połączenie zewnętrzne   zadeklarowany w jednej jednostce tłumaczeniowej, deklarowany jest we wszystkich jednostkach tłumaczeniowych, w których się znajduje; diagnostyka nie jest wymagana. Funkcja inline z zewnętrznym połączeniem musi mieć ten sam adres we wszystkich jednostkach tłumaczeniowych. Statyczna zmienna lokalna w zewnętrznej funkcji inline zawsze odnosi się do tego samego obiektu. Literał łańcuchowy w ciele zewnętrznej funkcji inline jest tym samym obiektem w różnych jednostkach tłumaczeniowych. [Uwaga: Literał łańcuchowy pojawiający się w domyślnym wyrażeniu argumentu nie znajduje się w ciele funkcji inline tylko dlatego, że wyrażenie jest używane w wywołaniu funkcji z tej funkcji inline. -End note]

Destylowane: może zawierać wiele definicji, ale musi mieć taki sam wygląd i styl każdy jednostka tłumaczeniowa i adres - ale to naprawdę nie daje ci wiele do radości. W związku z tym istnieje wiele przypadków deinicjalizacji jednostek tłumaczeniowych Nie określono (uwaga: nie mówię, że powołujesz się na UB, jeszcze).

Jeśli chodzi o virtual thingy - nie będzie żadnych inline. Kropka.

Standard mówi:

  • To samo deklaracja musi być dostępny
  • Musi być jedna definicja

Od MSDN:

Dana funkcja inline member musi być zadeklarowana w ten sam sposób w każdej jednostce kompilacji. To ograniczenie powoduje, że funkcje wbudowane zachowują się tak, jakby były instancjami. Dodatkowo musi istnieć dokładnie jedna definicja funkcji inline.

Twój A.h zawiera definicję klasy i członka foo()Definicja.

U1.cpp i U2.cpp obie definiować dwa różne obiekty klasy A.

Stworzysz kolejną A obiekt w main(). To jest w porządku.

Do tej pory widziałem tylko jedną definicję A::foo() który jest wbudowany. (Pamiętaj, że funkcja zdefiniowana w deklaracji klasy jest zawsze zaznaczone, czy jest poprzedzone, czy nie inline słowo kluczowe.)


3
2018-03-02 18:20



Przepraszam, ale nie o to chodzi. Pytanie dotyczy "instancji A :: foo ()", która musi być co najmniej jedna, ponieważ metody wirtualne muszą mieć instancję. - EFraim
Twój A :: foo () jest przedstawione przynajmniej w odniesieniu do przykładu. Czy mówisz o czystych wirtualach? - dirkgently
Powinien być zaznaczony w miejscu wywołania main, tak, ale powinna istnieć instancja utworzona, przynajmniej w wersji Debug, jeśli kompilator nie jest absolutnie pewny, że klasa nigdy nie będzie używana przez wskaźnik. Oczywiście wersja Release może usunąć tę instancję. - EFraim
Co masz na myśli mówiąc o instancji funkcji? - dirkgently
Wymuszona instancja, gdy kompilator nie inicjuje połączenia (na przykład wywołanie wirtualne za pomocą wskaźnika) - EFraim


Nie wpisuj swoich funkcji, jeśli chcesz się upewnić, że zostaną wkompilowane w konkretną bibliotekę.


2
2018-03-02 18:19