Dlaczego nie mogę zdefiniować funkcji w innej funkcji?

80

To nie jest kwestia funkcji lambda, wiem, że mogę przypisać lambdę do zmiennej.

Jaki jest sens pozwalania nam deklarować, ale nie definiować funkcji w kodzie?

Na przykład:

#include <iostream>

int main()
{
    // This is illegal
    // int one(int bar) { return 13 + bar; }

    // This is legal, but why would I want this?
    int two(int bar);

    // This gets the job done but man it's complicated
    class three{
        int m_iBar;
    public:
        three(int bar):m_iBar(13 + bar){}
        operator int(){return m_iBar;}
    }; 

    std::cout << three(42) << '\n';
    return 0;
}

Chcę więc wiedzieć, dlaczego C ++ miałby zezwalać na to, twoco wydaje się bezużyteczne i threeco wydaje się znacznie bardziej skomplikowane, ale nie zezwala one?

EDYTOWAĆ:

Z odpowiedzi wynika, że ​​deklaracja w kodzie może być w stanie zapobiec zanieczyszczeniu przestrzeni nazw, ale miałem nadzieję, że dowiem się, dlaczego możliwość deklarowania funkcji jest dozwolona, ​​ale możliwość definiowania funkcji jest niedozwolona.

Jonathan Mee
źródło
3
Pierwsza oneto definicja funkcji , pozostałe dwie to deklaracje .
Jakiś programista,
9
Myślę, że zrozumiałeś te terminy w niewłaściwy sposób - chcesz zapytać „Jaki jest sens pozwalania nam na deklarowanie, ale nie definiowanie funkcji w kodzie?” A skoro już o tym mowa, prawdopodobnie masz na myśli „wewnątrz funkcji ”. To wszystko „kod”.
Peter - Przywróć Monikę
14
Jeśli pytasz, dlaczego język ma dziwactwa i niespójności: ponieważ ewoluował przez kilka dziesięcioleci, dzięki pracy wielu ludzi z wieloma różnymi pomysłami, z języków wymyślonych z różnych powodów w różnym czasie. Jeśli pytasz, dlaczego ma to szczególne dziwactwo: ponieważ nikt (jak dotąd) nie uważał, że definicje funkcji lokalnych są wystarczająco przydatne do standaryzacji.
Mike Seymour
4
@MikeSeymour ma rację. C nie ma tak dobrej struktury jak, powiedzmy, Pascal i zawsze dopuszcza tylko definicje funkcji najwyższego poziomu. Tak więc powód jest historyczny, a ponadto brakuje potrzeby jego zmiany. To, że deklaracje funkcji są możliwe, jest tylko konsekwencją tego, że deklaracje zakresu są w ogóle możliwe. Zakazanie tego w przypadku funkcji oznaczałoby dodatkową zasadę.
Peter - Przywróć Monikę
3
@JonathanMee: Prawdopodobnie dlatego, że generalnie deklaracje są dozwolone w blokach i nie ma konkretnego powodu, aby zabraniać deklaracji funkcji; prościej jest po prostu zezwolić na dowolną deklarację bez specjalnych przypadków. Ale „dlaczego” nie jest tak naprawdę pytaniem, na które można odpowiedzieć; język jest tym, czym jest, ponieważ tak ewoluował.
Mike Seymour

Odpowiedzi:

41

Nie jest oczywiste, dlaczego onenie jest to dozwolone; funkcje zagnieżdżone zostały zaproponowane dawno temu w N0295, który mówi:

Omówimy wprowadzenie funkcji zagnieżdżonych do C ++. Zagnieżdżone funkcje są dobrze rozumiane, a ich wprowadzenie wymaga niewielkiego wysiłku ze strony producentów kompilatorów, programistów lub komitetu. Zagnieżdżone funkcje oferują znaczne korzyści, [...]

Oczywiście ta propozycja została odrzucona, ale ponieważ nie mamy protokołu ze spotkania dostępnego online 1993, ponieważ nie mamy możliwego źródła uzasadnienia tego odrzucenia.

W rzeczywistości ta propozycja jest odnotowana w wyrażeniach lambda i domknięciach dla C ++ jako możliwa alternatywa:

Jeden artykuł [Bre88] i propozycja N0295 skierowana do komisji C ++ [SH93] sugerują dodanie funkcji zagnieżdżonych do C ++. Funkcje zagnieżdżone są podobne do wyrażeń lambda, ale są zdefiniowane jako instrukcje w treści funkcji, a wynikowego zamknięcia nie można używać, chyba że ta funkcja jest aktywna. Te propozycje również nie obejmują dodawania nowego typu dla każdego wyrażenia lambda, ale zamiast tego implementują je bardziej jak zwykłe funkcje, w tym zezwalając na specjalny rodzaj wskaźnika funkcji do odwoływania się do nich. Obie te propozycje poprzedzają dodanie szablonów do C ++, więc nie wspominają o wykorzystaniu funkcji zagnieżdżonych w połączeniu z algorytmami ogólnymi. Ponadto te propozycje nie mają możliwości skopiowania zmiennych lokalnych do zamknięcia, więc zagnieżdżone funkcje, które generują, są całkowicie bezużyteczne poza ich funkcją zamykającą

Biorąc pod uwagę, że mamy teraz lambdy, jest mało prawdopodobne, abyśmy zobaczyli funkcje zagnieżdżone, ponieważ, jak zarysowano w artykule, są one alternatywami dla tego samego problemu, a funkcje zagnieżdżone mają kilka ograniczeń w stosunku do lambd.

Jeśli chodzi o tę część twojego pytania:

// This is legal, but why would I want this?
int two(int bar);

Istnieją przypadki, w których byłby to przydatny sposób wywołania żądanej funkcji. Wersja robocza sekcji standardowej C ++ 3.4.1 [basic.lookup.unqual] podaje jeden interesujący przykład:

namespace NS {
    class T { };
    void f(T);
    void g(T, int);
}

NS::T parm;
void g(NS::T, float);

int main() {
    f(parm); // OK: calls NS::f
    extern void g(NS::T, float);
    g(parm, 1); // OK: calls g(NS::T, float)
}
Shafik Yaghmour
źródło
1
Pytanie z podanego przykładu 3.4.1: Czy wywołujący w main nie może po prostu pisać ::g(parm, 1)w celu wywołania funkcji w globalnej przestrzeni nazw? Lub sprawdź, g(parm, 1.0f);co powinno dać lepsze dopasowanie do pożądanego g?
Peter - Przywróć Monikę
@PeterSchneider Zrobiłem tam zbyt mocne stwierdzenie, poprawiłem je.
Shafik Yaghmour
1
W tym miejscu chciałbym dodać komentarz: Ta odpowiedź została zaakceptowana nie dlatego, że najlepiej wyjaśniła, dlaczego w kodzie deklaracje funkcji są dozwolone; ale ponieważ wykonał najlepszą robotę, opisując, dlaczego w kodzie definicje funkcji nie są dozwolone, co było w rzeczywistości pytaniem. W szczególności wyjaśnia, dlaczego hipotetyczna implementacja funkcji w kodzie różniłaby się od implementacji lambd. +1
Jonathan Mee
1
@JonathanMee: Jak to się dzieje na świecie: „... nie mamy możliwego źródła uzasadnienia tego odrzucenia”. kwalifikuje się jako najlepsza praca polegająca na opisaniu, dlaczego definicje funkcji zagnieżdżonych są niedozwolone (lub nawet próba ich opisania w ogóle?)
Jerry Coffin
@JerryCoffin Odpowiedź zawierała oficjalne uzasadnienie, dlaczego lambdy są już super zbiorem definicji funkcji w kodzie, co sprawia, że ​​ich implementacja jest niepotrzebna: „Wynikowe zamknięcie nie może być używane, jeśli ta funkcja nie jest aktywna ... Ponadto te propozycje nie mają możliwości skopiowania zmienne lokalne do zamknięcia. " Zakładam, że pytasz, dlaczego twoja analiza dodatkowej złożoności nałożonej na kompilatory nie była odpowiedzią, którą zaakceptowałem. Jeśli tak: mówisz o trudności czegoś, co już osiągają lambdy, w kodzie definicje mogą być wyraźnie zaimplementowane dokładnie tak, jak lambdy.
Jonathan Mee
31

Cóż, odpowiedź brzmi „z powodów historycznych”. W C możesz mieć deklaracje funkcji w zakresie blokowym, a projektanci C ++ nie widzieli korzyści z usunięcia tej opcji.

Przykładowe użycie to:

#include <iostream>

int main()
{
    int func();
    func();
}

int func()
{
    std::cout << "Hello\n";
}

IMO to zły pomysł, ponieważ łatwo o błąd, dostarczając deklarację niezgodną z rzeczywistą definicją funkcji, co prowadzi do niezdefiniowanego zachowania, które nie zostanie zdiagnozowane przez kompilator.

MM
źródło
10
„Generalnie uważa się to za zły pomysł” - wymagane źródło
Richard Hodges
4
@RichardHodges: Cóż, deklaracje funkcji należą do plików nagłówkowych, a implementacja w plikach .c lub .cpp, więc umieszczanie tych deklaracji w definicjach funkcji narusza jedną z tych dwóch wytycznych.
MSalters
2
W jaki sposób zapobiega to odbieganiu deklaracji od definicji?
Richard Hodges
1
@JonathanMee: Mówię, że jeśli deklaracja, której używasz, nie jest dostępna w miejscu zdefiniowania funkcji, kompilator może nie sprawdzać, czy deklaracja pasuje do definicji. Więc możesz mieć lokalną deklarację some_type f();i definicję w innej jednostce tłumaczeniowej another_type f() {...}. Kompilator nie może ci powiedzieć, że te nie pasują, a wywołanie fz niewłaściwą deklaracją da niezdefiniowane zachowanie. Dlatego dobrym pomysłem jest mieć tylko jedną deklarację w nagłówku i dołączyć ten nagłówek, w którym funkcja jest zdefiniowana, a także miejsce, w którym jest używana.
Mike Seymour
6
Myślę, że mówisz, że powszechna praktyka umieszczania deklaracji funkcji w plikach nagłówkowych jest ogólnie użyteczna. Nie sądzę, żeby ktokolwiek się z tym nie zgodził. Nie widzę powodu dla stwierdzenia, że ​​deklarowanie funkcji zewnętrznej w zakresie funkcji jest „ogólnie uważane za zły pomysł”.
Richard Hodges
23

W podanym przykładzie, void two(int)jest deklarowana jako funkcja zewnętrzna, przy czym ta deklaracja jest ważna tylko w zakresie mainfunkcji .

Jest to rozsądne, jeśli chcesz, aby nazwa była twodostępna tylko w domeniemain() , aby uniknąć zanieczyszczenia globalnej przestrzeni nazw w bieżącej jednostce kompilacji.

Przykład w odpowiedzi na komentarze:

main.cpp:

int main() {
  int foo();
  return foo();
}

foo.cpp:

int foo() {
  return 0;
}

nie ma potrzeby plików nagłówkowych. kompilować i łączyć z

c++ main.cpp foo.cpp 

skompiluje się i uruchomi, a program zwróci 0 zgodnie z oczekiwaniami.

Richarda Hodgesa
źródło
Czy nie musiałoby być tworównież zdefiniowane w pliku, powodując w ten sposób zanieczyszczenie?
Jonathan Mee
1
@JonathanMee nie, two()można zdefiniować w zupełnie innej jednostce kompilacji.
Richard Hodges
Potrzebuję pomocy w zrozumieniu, jak to zadziała. Czy nie musiałbyś dołączyć nagłówka, w którym został zadeklarowany? W którym momencie zostanie to ogłoszone, prawda? Po prostu nie widzę, jak można to zdefiniować w kodzie i jakoś nie dołączyć pliku, który to deklaruje?
Jonathan Mee
5
@JonathanMee Nie ma nic specjalnego w nagłówkach. Są po prostu wygodnym miejscem do składania deklaracji. Deklaracja w funkcji jest tak samo ważna jak deklaracja w nagłówku. Więc nie, nie musisz dołączać nagłówka tego, do czego tworzysz łącze (może nawet nie być nagłówka).
Sześcienny
1
@JonathanMee W języku C / C ++ definicja i implementacja to to samo. Możesz deklarować funkcję tak często, jak chcesz, ale możesz ją zdefiniować tylko raz. Deklaracja nie musi znajdować się w pliku kończącym się na .h - możesz mieć plik use.cpp, który ma pasek funkcji, który wywołuje foo (deklarując foo w treści), oraz plik dostarcza .cpp, który definiuje foo, i będzie działać dobrze, o ile nie zepsujesz kroku łączenia.
Sześcienny
19

Możesz robić te rzeczy, głównie dlatego, że nie są one wcale takie trudne.

Z punktu widzenia kompilatora, umieszczenie deklaracji funkcji wewnątrz innej funkcji jest dość proste do zaimplementowania. Kompilator potrzebuje mechanizmu, aby zezwalać deklaracjom wewnątrz funkcji na obsługę innych deklaracji (np. int x;) Wewnątrz funkcji i tak.

Zazwyczaj ma ogólny mechanizm analizowania deklaracji. Dla faceta piszącego kompilator nie ma znaczenia, czy ten mechanizm jest wywoływany podczas analizowania kodu wewnątrz, czy na zewnątrz innej funkcji - to tylko deklaracja, więc kiedy widzi wystarczająco dużo, aby wiedzieć, co jest deklaracją, wywołuje część kompilatora, która zajmuje się deklaracjami.

W rzeczywistości zakazanie tych konkretnych deklaracji wewnątrz funkcji prawdopodobnie zwiększyłoby złożoność, ponieważ kompilator potrzebowałby wówczas całkowicie nieuzasadnionego sprawdzenia, czy już patrzy na kod wewnątrz definicji funkcji i na tej podstawie decyduje, czy zezwolić, czy zabronić tego konkretnego deklaracja.

Pozostaje pytanie, czym różni się funkcja zagnieżdżona. Zagnieżdżona funkcja różni się tym, jak wpływa na generowanie kodu. W językach, które zezwalają na funkcje zagnieżdżone (np. Pascal), normalnie oczekuje się, że kod funkcji zagnieżdżonej ma bezpośredni dostęp do zmiennych funkcji, w której jest zagnieżdżona. Na przykład:

int foo() { 
    int x;

    int bar() { 
        x = 1; // Should assign to the `x` defined in `foo`.
    }
}

Bez funkcji lokalnych kod dostępu do zmiennych lokalnych jest dość prosty. W typowej implementacji, gdy wykonanie wchodzi do funkcji, na stosie alokowany jest pewien blok miejsca na zmienne lokalne. Wszystkie zmienne lokalne są alokowane w tym pojedynczym bloku, a każda zmienna jest traktowana jako po prostu przesunięcie od początku (lub końca) bloku. Na przykład rozważmy funkcję podobną do tej:

int f() { 
   int x;
   int y;
   x = 1;
   y = x;
   return y;
}

Kompilator (zakładając, że nie zoptymalizował dodatkowego kodu) może wygenerować kod odpowiadający z grubsza temu:

stack_pointer -= 2 * sizeof(int);      // allocate space for local variables
x_offset = 0;
y_offset = sizeof(int);

stack_pointer[x_offset] = 1;                           // x = 1;
stack_pointer[y_offset] = stack_pointer[x_offset];     // y = x;
return_location = stack_pointer[y_offset];             // return y;
stack_pointer += 2 * sizeof(int);

W szczególności ma jeden lokalizację wskazującą początek bloku zmiennych lokalnych, a cały dostęp do zmiennych lokalnych jest przesunięty z tej lokalizacji.

W przypadku funkcji zagnieżdżonych tak już nie jest - zamiast tego funkcja ma dostęp nie tylko do swoich własnych zmiennych lokalnych, ale także do zmiennych lokalnych dla wszystkich funkcji, w których jest zagnieżdżona. Zamiast mieć tylko jeden „stack_pointer”, na podstawie którego oblicza przesunięcie, musi przejść z powrotem w górę stosu, aby znaleźć wskaźniki stack_pointers lokalne dla funkcji, w których jest zagnieżdżony.

Teraz, w trywialnym przypadku, który też nie jest aż tak straszny - jeśli barjest zagnieżdżony w środku foo, barmoże po prostu spojrzeć na stos na poprzedni wskaźnik stosu, aby uzyskać dostęp do foozmiennych. Dobrze?

Źle! Cóż, są przypadki, w których może to być prawdą, ale niekoniecznie tak jest. W szczególności,bar może być rekurencyjny, w którym to przypadku dane wywołaniebarbyć może trzeba będzie spojrzeć na prawie dowolną liczbę poziomów na stosie, aby znaleźć zmienne otaczającej funkcji. Ogólnie rzecz biorąc, musisz zrobić jedną z dwóch rzeczy: albo umieścić dodatkowe dane na stosie, aby mógł przeszukiwać stos w czasie wykonywania, aby znaleźć ramkę stosu otaczającej funkcji, albo efektywnie przekażesz wskaźnik do ramka stosu funkcji otaczającej jako ukryty parametr funkcji zagnieżdżonej. Och, ale niekoniecznie istnieje też tylko jedna otaczająca funkcja - jeśli możesz zagnieżdżać funkcje, prawdopodobnie możesz zagnieżdżać je (mniej lub bardziej) dowolnie głęboko, więc musisz być gotowy do przekazania dowolnej liczby ukrytych parametrów. Oznacza to, że zazwyczaj kończy się coś w rodzaju listy ramek stosu połączonych z otaczającymi funkcjami,

Oznacza to jednak, że dostęp do zmiennej „lokalnej” może nie być sprawą trywialną. Znalezienie właściwej ramki stosu, aby uzyskać dostęp do zmiennej, może być nietrywialne, więc dostęp do zmiennych otaczających funkcji jest również (przynajmniej zwykle) wolniejszy niż dostęp do zmiennych prawdziwie lokalnych. I, oczywiście, kompilator musi wygenerować kod, aby znaleźć odpowiednie ramki stosu, uzyskać dostęp do zmiennych za pośrednictwem dowolnej liczby ramek stosu i tak dalej.

To jest złożoność, której C unikał, zakazując zagnieżdżonych funkcji. Z pewnością prawdą jest, że obecny kompilator C ++ jest raczej innym rodzajem bestii niż klasyczny kompilator C. z lat 70. W przypadku dziedziczenia wielokrotnego, wirtualnego, kompilator C ++ musi w każdym przypadku radzić sobie z rzeczami o tej samej ogólnej naturze (tj. Znalezienie lokalizacji zmiennej klasy bazowej w takich przypadkach również może być nietrywialne). W ujęciu procentowym obsługa funkcji zagnieżdżonych nie zwiększyłaby zbytnio złożoności obecnego kompilatora C ++ (a niektóre, takie jak gcc, już je obsługują).

Jednocześnie rzadko dodaje też wiele użyteczności. W szczególności, jeśli chcesz zdefiniować coś, co działa jak funkcja wewnątrz funkcji, możesz użyć wyrażenia lambda. To, co to faktycznie tworzy, to obiekt (tj. Instancja jakiejś klasy), który przeciąża operator wywołania funkcji ( operator()), ale nadal daje funkcje podobne do funkcji. Sprawia jednak, że przechwytywanie (lub nie) danych z otaczającego kontekstu jest bardziej wyraźne, co pozwala mu korzystać z istniejących mechanizmów zamiast wymyślać zupełnie nowy mechanizm i zestaw reguł ich użycia.

Konkluzja: nawet jeśli początkowo mogłoby się wydawać, że deklaracje zagnieżdżone są trudne, a funkcje zagnieżdżone są trywialne, prawdą jest mniej więcej na odwrót: funkcje zagnieżdżone są w rzeczywistości znacznie bardziej skomplikowane w obsłudze niż deklaracje zagnieżdżone.

Jerry Coffin
źródło
5

Pierwsza to definicja funkcji i jest niedozwolona. Oczywiście wt to użycie umieszczenia definicji funkcji wewnątrz innej funkcji.

Ale pozostałe dwójki to tylko deklaracje. Wyobraź sobie, że musisz użyć int two(int bar);funkcji w głównej metodzie. Ale jest zdefiniowana poniżej main()funkcji, więc deklaracja funkcji wewnątrz funkcji sprawia, że ​​możesz używać tej funkcji z deklaracjami.

To samo dotyczy trzeciego. Deklaracje klasy wewnątrz funkcji pozwalają na użycie klasy wewnątrz funkcji bez podawania odpowiedniego nagłówka lub odwołania.

int main()
{
    // This is legal, but why would I want this?
    int two(int bar);

    //Call two
    int x = two(7);

    class three {
        int m_iBar;
        public:
            three(int bar):m_iBar(13 + bar) {}
            operator int() {return m_iBar;}
    };

    //Use class
    three *threeObj = new three();

    return 0;
}
ANjaNA
źródło
2
Co to jest „spowolnienie”? Czy masz na myśli „deklarację”?
Peter Mortensen
4

Ta funkcja języka została odziedziczona z C, gdzie służyła do jakiegoś celu we wczesnych latach C (może określanie zakresu deklaracji funkcji?) . Nie wiem, czy ta funkcja jest często używana przez współczesnych programistów C i szczerze w to wątpię.

Podsumowując odpowiedź:

nie ma celu dla tej funkcji we współczesnym C ++ (o którym przynajmniej wiem), jest tutaj z powodu wstecznej kompatybilności C ++ - to-C (chyba :)).


Dzięki poniższemu komentarzowi:

Prototyp funkcji jest ograniczony do funkcji, w której jest zadeklarowany, więc można mieć bardziej uporządkowaną globalną przestrzeń nazw - odwołując się do zewnętrznych funkcji / symboli bez #include.

pan.pd
źródło
celem jest kontrolowanie zakresu nazwy, aby uniknąć zanieczyszczenia globalnej przestrzeni nazw.
Richard Hodges
Ok, przypuszczam, że jest to przydatne w sytuacjach, gdy chcesz odwoływać się do zewnętrznych funkcji / symboli bez zanieczyszczania globalnej przestrzeni nazw przez #include! Dzięki za wskazanie tego. Zrobię edycję.
mr.pd
4

W rzeczywistości istnieje jeden przypadek użycia, który jest prawdopodobnie przydatny. Jeśli chcesz mieć pewność, że zostanie wywołana określona funkcja (a Twój kod się skompiluje), bez względu na to, co deklaruje otaczający kod, możesz otworzyć własny blok i zadeklarować w nim prototyp funkcji. (Inspiracja pochodzi od Johannesa Schauba, https://stackoverflow.com/a/929902/3150802 , przez TeKa, https://stackoverflow.com/a/8821992/3150802 ).

Może to być szczególnie przydatne, jeśli musisz dołączyć nagłówki, których nie kontrolujesz, lub jeśli masz makro wielowierszowe, które może być używane w nieznanym kodzie.

Kluczem jest to, że deklaracja lokalna zastępuje poprzednie deklaracje w najbardziej wewnętrznym otaczającym bloku. Chociaż może to wprowadzać subtelne błędy (i, jak sądzę, jest zabronione w C #), może być używane świadomie. Rozważać:

// somebody's header
void f();

// your code
{   int i;
    int f(); // your different f()!
    i = f();
    // ...
}

Linkowanie może być interesujące, ponieważ są szanse, że nagłówki należą do biblioteki, ale myślę, że możesz dostosować argumenty linkera, aby f()została rozwiązana dla twojej funkcji do czasu rozważenia tej biblioteki. Albo każesz mu ignorować zduplikowane symbole. Lub nie umieszczasz linków do biblioteki.

Peter - przywróć Monikę
źródło
Więc pomóż mi tutaj, gdzie zostałby fzdefiniowany w twoim przykładzie? Czy nie skończyłbym z błędem redefinicji funkcji, ponieważ różnią się one tylko typem zwracania?
Jonathan Mee
@JonathanMee hmmm ... f () można zdefiniować w innej jednostce tłumaczeniowej, pomyślałem. Ale prawdopodobnie linker by się wzdrygnął, gdybyś również zlinkował z zakładaną biblioteką, zakładam, że masz rację. Więc nie możesz tego zrobić ;-), a przynajmniej musisz zignorować wiele definicji.
Peter - Przywróć Monikę
Zły przykład. Nie ma rozróżnienia między void f()iw int f()C ++, ponieważ zwracana wartość funkcji nie jest częścią podpisu funkcji w C ++. Zmień drugą deklarację na, int f(int)a wycofam swój głos przeciw.
David Hammen
@DavidHammen Spróbuj skompilować i = f();po zadeklarowaniu void f(). „Bez rozróżnienia” to tylko połowa prawdy ;-). Właściwie użyłem „sygnatur” funkcji, które nie są przeciążalne, ponieważ w przeciwnym razie cała okoliczność byłaby niepotrzebna w C ++, ponieważ dwie funkcje z różnymi typami parametrów / numerami mogłyby szczęśliwie współistnieć.
Peter - Przywróć Monikę
@DavidHammen Rzeczywiście, po przeczytaniu odpowiedzi Shafika uważam, że mamy trzy przypadki: 1. Podpis różni się parametrami. Żaden problem w C ++, proste przeciążenie i reguły najlepszego dopasowania działają. 2. Podpis wcale się nie różni. Brak problemu na poziomie językowym; funkcja jest rozwiązana przez połączenie z pożądaną implementacją. 3. Różnica dotyczy tylko typu zwracanego. Nie jest to problem na poziomie językowym, jak wykazano; rozdzielczość przeciążenia nie działa; musimy odpowiednio zadeklarować funkcję z innym podpisem i linkiem.
Peter - Przywróć Monikę
3

Nie jest to odpowiedź na pytanie PO, ale raczej odpowiedź na kilka uwag.

Nie zgadzam się z tymi punktami w komentarzach i odpowiedziach: 1 że deklaracje zagnieżdżone są rzekomo nieszkodliwe, oraz 2 że definicje zagnieżdżone są bezużyteczne.

1 Głównym kontrprzykładem rzekomej nieszkodliwości zagnieżdżonych deklaracji funkcji jest niesławna Najbardziej irytująca analiza . IMO rozprzestrzenianie się zamieszania spowodowanego przez to wystarczy, aby uzasadnić dodatkową regułę zabraniającą zagnieżdżonych deklaracji.

2 Pierwszym kontrprzykładem na rzekomą bezużyteczność zagnieżdżonych definicji funkcji jest częsta potrzeba wykonania tej samej operacji w kilku miejscach wewnątrz dokładnie jednej funkcji. Istnieje oczywiste obejście tego problemu:

private:
inline void bar(int abc)
{
    // Do the repeating operation
}

public: 
void foo()
{
    int a, b, c;
    bar(a);
    bar(b);
    bar(c);
}

Jednak to rozwiązanie często zanieczyszcza definicję klasy licznymi funkcjami prywatnymi, z których każda jest używana dokładnie w jednym wywołującym. Deklaracja funkcji zagnieżdżonej byłaby znacznie czystsza.

Michael
źródło
1
Myślę, że to dobre podsumowanie motywacji mojego pytania. Jeśli spojrzysz na oryginalną wersję, którą zacytowałem MVP, ale w komentarzach (na moim własnym pytaniu) ciągle mnie unieważnia, mówi się, że MVP jest nieistotny :( Po prostu nie mogę dowiedzieć się, jak potencjalnie szkodliwe w deklaracjach kodu są nadal tutaj , ale potencjalnie przydatne w definicjach kodu nie są. Dałem ci +1 za przydatne przykłady.
Jonathan Mee
2

W szczególności odpowiadając na to pytanie:

Z odpowiedzi wynika, że ​​deklaracja w kodzie może być w stanie zapobiec zanieczyszczeniu przestrzeni nazw, ale miałem nadzieję, że dowiem się, dlaczego możliwość deklarowania funkcji jest dozwolona, ​​ale możliwość definiowania funkcji jest niedozwolona.

Ponieważ rozważ ten kod:

int main()
{
  int foo() {

    // Do something
    return 0;
  }
  return 0;
}

Pytania do projektantów języków:

  1. Czy powinny foo()być dostępne dla innych funkcji?
  2. Jeśli tak, jaka powinna być jego nazwa? int main(void)::foo()?
  3. (Zauważ, że 2 nie byłoby możliwe w C, twórcy C ++)
  4. Jeśli chcemy funkcji lokalnej, mamy już sposób - uczynić ją statycznym składnikiem klasy zdefiniowanej lokalnie. Czy zatem powinniśmy dodać inną syntaktyczną metodę osiągnięcia tego samego wyniku? Dlaczego to robisz? Czy nie zwiększyłoby to obciążenia związanego z konserwacją deweloperów kompilatorów C ++?
  5. I tak dalej...
Richarda Hodgesa
źródło
Oczywiście to zachowanie jest zdefiniowane dla lambd? Dlaczego nie funkcje zdefiniowane w kodzie?
Jonathan Mee
Lambda jest tylko skrótem do pisania obiektu funkcji. Specjalny przypadek lambdy, która nie przechwytuje argumentów, jest równoważny z definicją funkcji lokalnej, tak jak pisanie obiektu funkcji, który nie ma elementów członkowskich danych.
Richard Hodges
Właśnie wskazując, że lambdy, a w kodzie deklarowane funkcje już odrzucić wszystkie swoje punkty. Nie powinno to zwiększać „ciężaru”.
Jonathan Mee
@JonathanMee, jeśli czujesz się z tym mocno, z całą pewnością prześlij RFC do komisji normalizacyjnej c ++.
Richard Hodges
Odpowiedź Shafika Yaghmoura dotyczyła tego, co już zostało zrobione. Osobiście chciałbym zobaczyć usunięcie możliwości deklarowania funkcji w kodzie, jeśli nie pozwalają nam ich zdefiniować. Odpowiedź Richarda Hodgesa dobrze radzi sobie z wyjaśnieniem, dlaczego nadal potrzebujemy możliwości deklarowania w deklaracji kodu.
Jonathan Mee
1

Chciałem tylko podkreślić, że kompilator GCC umożliwia deklarowanie funkcji wewnątrz funkcji. Przeczytaj więcej na ten temat tutaj . Również wraz z wprowadzeniem lambd do C ++, to pytanie jest teraz nieco przestarzałe.


Możliwość zadeklarowania nagłówków funkcji w innych funkcjach, okazała się przydatna w następującym przypadku:

void do_something(int&);

int main() {
    int my_number = 10 * 10 * 10;
    do_something(my_number);

    return 0;
}

void do_something(int& num) {
    void do_something_helper(int&); // declare helper here
    do_something_helper(num);

    // Do something else
}

void do_something_helper(int& num) {
    num += std::abs(num - 1337);
}

Co my tu mamy? Zasadniczo masz funkcję, która powinna być wywoływana z main, więc to, co robisz, to deklarowanie jej jako normalnej. Ale potem zdajesz sobie sprawę, że ta funkcja potrzebuje również innej funkcji, która pomoże jej w tym, co robi. Więc zamiast deklarować tę funkcję pomocniczą powyżej main, deklarujesz ją wewnątrz funkcji, która jej potrzebuje, a następnie można ją wywołać z tej funkcji i tylko z tej funkcji.

Chodzi mi o to, że deklarowanie nagłówków funkcji wewnątrz funkcji może być pośrednią metodą hermetyzacji funkcji, która pozwala funkcji ukryć niektóre części tego, co robi, delegując do innej funkcji, której tylko ona jest świadoma, prawie dając złudzenie zagnieżdżonego funkcji .

smac89
źródło
Zrozumiałem, że możemy zdefiniować wbudowaną lambdę. Zrozumiałem, że możemy zadeklarować funkcję inline, ale to jest pochodzenie najbardziej irytującej analizy , więc moje pytanie brzmiało, czy standard ma zachować funkcjonalność, która służy tylko do wywoływania gniewu u programistów, czy programiści nie powinni być w stanie zdefiniować funkcja też wbudowana? Odpowiedź Richarda Hodgesa pomogła mi zrozumieć źródło tego problemu.
Jonathan Mee
0

Deklaracje funkcji zagnieżdżonych są dozwolone prawdopodobnie dla 1. Odwołań do przodu 2. Aby móc zadeklarować wskaźnik do funkcji i przekazać inne funkcje w ograniczonym zakresie.

Definicje funkcji zagnieżdżonych są niedozwolone prawdopodobnie z powodu problemów takich jak 1. Optymalizacja 2. Rekursja (zamykanie i zagnieżdżanie zdefiniowanych funkcji) 3. Ponowne wejście 4. Współbieżność i inne problemy z dostępem do wielu wątków.

Z mojego ograniczonego zrozumienia :)

Gwiazda neutronowa
źródło