Dlaczego C++ nie obsługuje funkcji zwracających tablice?

Niektóre języki umożliwiają po prostu zadeklarowanie funkcji zwracającej tablicę, jak normalna funkcja, jak Java:

public String[] funcarray() {
   String[] test = new String[]{"hi", "hello"};
   return test;
}

Dlaczego C++ nie obsługuje czegoś takiego jak int[] funcarray(){} ? Możesz zwrócić tablicę, ale utworzenie takiej funkcji jest naprawdę kłopotliwe. Słyszałem też gdzieś, że ciągi są tylko tablicami znaków. Więc jeśli możesz zwrócić ciąg znaków w C++, dlaczego nie tablicę?

Author: Lockhead, 2011-03-01

10 answers

Założę się, że mówiąc zwięźle, była to po prostu decyzja projektowa. Mówiąc dokładniej, jeśli naprawdę chcesz wiedzieć dlaczego, musisz pracować od podstaw.

Pomyślmy najpierw o C. W języku C istnieje wyraźne rozróżnienie między "pass by reference" I "pass by value". Aby potraktować to lekko, nazwa tablicy w C jest tak naprawdę tylko wskaźnikiem. Dla wszystkich intencji i celów, różnica (ogólnie) sprowadza się do alokacji. Kod
int array[n];

Would Utwórz 4 * n bajtów pamięci (w systemie 32-bitowym) na stosie skorelowanym z zakresem dowolnego bloku kodu składającego deklarację. Z kolei

int* array = (int*) malloc(sizeof(int)*n);

Utworzy taką samą ilość pamięci, ale na stercie. W tym przypadku to, co znajduje się w tej pamięci, nie jest związane z zakresem, tylko odniesienie do pamięci jest ograniczone zakresem. Tutaj pojawia się pass by value I pass by reference. Przekazywanie przez wartość, jak zapewne wiesz, oznacza, że gdy coś jest przekazywane lub zwracane z funkcja," rzecz", która jest przekazywana jest wynikiem oceny zmiennej. Innymi słowy,

int n = 4;
printf("%d", n);

Wyświetli liczbę 4, ponieważ konstrukt n ocenia na 4 (przepraszam, jeśli to elementarne, chcę tylko pokryć wszystkie bazy). Ta 4 nie ma absolutnie żadnego związku ani związku z przestrzenią pamięci Twojego programu, jest po prostu dosłowna, więc gdy opuścisz zakres, w którym Ta 4 ma kontekst, tracisz go. A co z "pass by reference"? Przejście przez odniesienie jest nie inaczej w kontekście funkcji; po prostu oceniasz konstrukcję, która zostanie przekazana. Jedyną różnicą jest to, że po dokonaniu oceny przekazanej "rzeczy", użytkownik używa wyniku oceny jako adresu pamięci. Miałem kiedyś pewnego cynicznego instruktora CS, który uwielbiał twierdzić, że nie ma czegoś takiego jak przekazywanie przez odniesienie, tylko sposób na przekazywanie mądrych wartości. Naprawdę, on ma rację. Więc teraz myślimy o zakresie w kategoriach funkcji. Udawaj, że możesz mieć powrót tablicy "type": "content"]}

int[] foo(args){
    result[n];
    // Some code
    return result;
}

Problem polega na tym, że wynik jest obliczany na adres 0 elementu tablicy. Ale kiedy próbujesz uzyskać dostęp do tej pamięci spoza tej funkcji (poprzez wartość zwracaną), masz problem, ponieważ próbujesz uzyskać dostęp do pamięci, która nie znajduje się w zakresie, z którym pracujesz (stos wywołania funkcji). Tak więc sposób, w jaki obejdziemy to jest ze standardowym "pass by reference" jiggery-pokery:

int* foo(args){
    int* result = (int*) malloc(sizeof(int)*n));
    // Some code
    return result;
}

Wciąż otrzymujemy adres pamięci wskazując na 0 element tablicy, ale teraz mamy dostęp do tej pamięci.

O co mi chodzi? W Javie powszechne jest twierdzenie ,że "wszystko jest przekazywane przez wartość". To prawda. Ten sam cyniczny instruktor z góry również miał to do powiedzenia o Javie i OOP w ogóle: wszystko jest tylko wskaźnikiem. I ma rację. Podczas gdy wszystko w Javie jest w rzeczywistości przekazywane przez wartość, prawie wszystkie te wartości są rzeczywiście adresami pamięci. Więc w Javie, język pozwala zwrócić array lub String, ale robi to, zamieniając go na wersję ze wskaźnikami dla Ciebie. Zarządza również twoją pamięcią. A automatyczne zarządzanie pamięcią, choć pomocne, nie jest wydajne.

To sprowadza nas do C++. Cały powód, dla którego C++ został wynaleziony, był taki, że Bjarne Stroustrup eksperymentował z Simulą (w zasadzie oryginalnym Oopl) podczas pracy doktorskiej i uważał, że jest fantastyczny koncepcyjnie, ale zauważył, że działa raczej okropnie. I tak zaczął pracować na co nazwano C z klasami, który został przemianowany na C++. W ten sposób, jego celem było stworzenie języka programowania, który wziął jedne z najlepszych funkcji z Simula, ale pozostał potężny i szybki. Zdecydował się rozszerzyć C ze względu na jego legendarną już wydajność, a jednym z kompromisów było to, że zdecydował się nie implementować automatycznego zarządzania pamięcią lub zbierania śmieci na tak dużą skalę, jak inne OOPL ' y. zwracanie tablicy z jednej z klas szablonów działa, ponieważ, cóż, używasz klasy. Ale jeśli chcesz zwrócić tablicę C, musisz to zrobić w sposób C. Innymi słowy, C++ obsługuje zwracanie tablicy dokładnie tak samo jak Java; po prostu nie wykonuje całej pracy za Ciebie. Bo Duńczyk myślał, że będzie za wolno.

 64
Author: Doug Stephen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-19 22:38:51

C++ GO obsługuje-no cóż:

vector< string> func()
{
   vector<string> res;
   res.push_back( "hello" );
   res.push_back( "world" );
   return res;
}

Nawet C go wspiera:

struct somearray
{
  struct somestruct d[50];
};

struct somearray func()
{
   struct somearray res;
   for( int i = 0; i < 50; ++i )
   {
      res.d[i] = whatever;
   }
   // fill them all in
   return res;
}

A std::string jest klasą, ale kiedy mówisz ciąg znaków, prawdopodobnie masz na myśli literał. Możesz bezpiecznie zwracać literal z funkcji, ale w rzeczywistości możesz statycznie utworzyć dowolną tablicę i zwrócić ją z funkcji. Byłoby to bezpieczne dla wątków, gdyby była to tablica const (tylko do odczytu), co ma miejsce w przypadku liter ciągów.

Tablica, którą zwracasz, ulegnie degradacji do wskaźnika, więc nie być w stanie obliczyć jego rozmiar już po powrocie.

Zwrócenie tablicy, jeśli byłoby to możliwe, musiałoby mieć stałą długość w pierwszej kolejności, biorąc pod uwagę, że kompilator musi utworzyć stos wywołań, a następnie ma problem, że tablice nie są wartościami l, więc otrzymanie jej w funkcji wywołującej musiałoby użyć nowej zmiennej z inicjalizacją, co jest niepraktyczne. Zwrot może być również niepraktyczny z tego samego powodu, chociaż mogli użyć specjalnej notacji do zwrotu wartości.

Pamiętaj, że na początku C wszystkie zmienne musiały być zadeklarowane u góry funkcji i nie można było zadeklarować po prostu pierwszego użycia. W tym czasie było to niewykonalne.

Dali obejście wprowadzenia tablicy do struktury i tak musi teraz pozostać w C++, ponieważ używa tej samej konwencji wywołania.

Uwaga: w językach takich jak Java, tablica jest klasą. Tworzysz nowy. Możesz je przypisać (są wartościami l).

 31
Author: CashCow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-02 11:22:22

Tablice w C (i w C++ dla kompatybilności wstecznej) mają specjalną semantykę, która różni się od pozostałych typów. W szczególności, podczas gdy dla pozostałych typów, C ma tylko semantykę pass-by-value, w przypadku tablic efekt składni pass-by-value symuluje pass-by-reference w dziwny sposób:

W sygnaturze funkcji argument typu Tablica N elementów typu T jest zamieniana na wskaźnik na t. W wywołaniu funkcji przekazującej tablicę jako argument do funkcji spowoduje rozpad tablicy na wskaźnik do pierwszego elementu , a wskaźnik ten zostanie skopiowany do funkcji.

Z powodu tego szczególnego traktowania tablic --nie mogą być przekazywane przez wartość--, nie mogą być również zwracane przez wartość. W C możesz zwrócić wskaźnik, a w c++ również referencję, ale sama tablica nie może być przydzielona do stosu.

Jeśli myślisz o tym, to nie różni się to od języka, którym jesteś używając w pytaniu, ponieważ tablica jest przydzielana dynamicznie i zwracasz tylko wskaźnik / odniesienie do niej.

Język C++, z drugiej strony, umożliwia różne rozwiązania tego konkretnego problemu, jak użycie std::vector w bieżącym standardzie (zawartość jest alokowana dynamicznie) lub std::array w nadchodzącym standardzie (zawartość może być alokowana w stosie, ale może to mieć większy koszt, ponieważ każdy element będzie musiał zostać skopiowany w przypadkach, gdy kopia nie może zostać skopiowana przez program. kompilator). W rzeczywistości można użyć tego samego typu podejścia z aktualnym standardem, używając gotowych bibliotek, takich jak boost::array.

 26
Author: David Rodríguez - dribeas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-01 16:53:03

"nie możesz zwrócić tablicy z funkcji, ponieważ tablica ta byłaby deklarowane wewnątrz funkcji, a jej lokalizacja byłaby wtedy stos rama. Jednak ramka stosu jest usuwana po zakończeniu funkcji. Funkcje muszą kopiowanie zwracanej wartości z ramki stosu do miejsce powrotu, a to nie możliwe z tablicami."

Z dyskusji tutaj:

Http://forum.codecall.net/c-c/32457-function-return-array-c.html

 8
Author: Brandon Frohbieter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-01 19:53:47

Inni mówili, że w C++, jeden używa wektora zamiast tablic odziedziczonych po C.

Więc dlaczego C++ nie pozwala zwracać tablic C? Ponieważ C nie.

Dlaczego C nie? Ponieważ C wyewoluowało z B, nietypowanego języka, w którym zwracanie tablicy nie ma żadnego sensu. Podczas dodawania typów do B, byłoby sensowne, aby umożliwić zwrócenie tablicy, ale nie zostało to zrobione w celu utrzymania niektórych idiomów B i ułatwienia konwersji programów z B do C. I od tego czasu odmówiono możliwości uczynienia tablic C bardziej użytecznymi, jak zawsze (a jeszcze bardziej, nawet nie rozważano), ponieważ złamałoby to zbyt wiele istniejącego kodu.

 7
Author: AProgrammer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-01 17:58:53

Możesz zwrócić wskaźnik do tablicy. Tylko uważaj, żeby później uwolnić pamięć.

public std::string* funcarray() {
    std::string* test = new std::string[2];
    test[0] = "hi";
    test[1] = "hello";
    return test;
}

// somewhere else:
std::string* arr = funcarray();
std::cout << arr[0] << " MisterSir" << std::endl;
delete[] arr;

Lub możesz po prostu użyć jednego z kontenerów w przestrzeni nazw std, jak std:: vector.

 3
Author: Jordi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-01 16:45:44

"dlaczego C++ nie obsługuje czegoś takiego": bo to nie miałoby sensu. W językach referencyjnych, takich jak JAVA czy PHP, zarządzanie pamięcią opiera się na usuwaniu śmieci. Części pamięci, które nie mają referencji (Żadna zmienna w programie nie wskazuje na nią więcej) są automatycznie zwalniane. W tym kontekście można przydzielić pamięć i przekazać referencję wokół carefreely.

Kod C++ zostanie przetłumaczony na kod maszynowy i nie ma w nim zdefiniowanego GC. Czyli w C i C++ istnieje silne poczucie własności bloków pamięci. Musisz wiedzieć, czy wskaźnik, który wybierzesz, jest twój do uwolnienia w dowolnym momencie (w rzeczywistości ty shoud uwolnij go po użyciu), Czy masz wskaźnik do współdzielonej części pamięci, która jest absolutnym Nie-Nie do uwolnienia.

W tym środowisku nie wygrasz nic z kreowaniem nieskończonych kopii tablicy za każdym razem, gdy przechodzi ona do iz funkcji. Bardziej skomplikowanym zadaniem jest zarządzanie tablicami danych w językach podobnych do C. Tam nie jest uniwersalnym rozwiązaniem i musisz wiedzieć, kiedy zwolnić pamięć.

Czy tablica zwracana przez funkcję jest zawsze kopią (Twoją do wolnej) czy trzeba ją kopiować? Whet czy wygrasz poprzez wstawienie tablicy wskaźnika do tablicy?

 2
Author: vbence,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-01 17:01:44

Zwraca std::vector<> zamiast tablicy. Ogólnie rzecz biorąc, tablice nie działają dobrze z C++ i generalnie należy ich unikać.

Ponadto, typ danych string nie jest tylko tablicą znaków, chociaż "cytowany ciąg" jest. string zarządza tablicą znaków i możesz uzyskać do niej dostęp za pomocą .c_str(), ale to nie wszystko.

 1
Author: David Thornley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-01 16:43:54
 1
Author: sgokhales,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:00:25

Wszystkie te odpowiedzi nie mają sensu. C++ po prostu go nie obsługuje. Nie obsługiwał nawet sposobu zwracania tablicy o rozmiarze statycznym przed std::array<T, N>. C++ Może wspierać zwracanie nawet tablic o dynamicznej wielkości, ale tak nie jest. jestem pewien, że są powody, dla których można się bronić, ale mogą.

Wystarczy przydzielić dynamiczną tablicę na stosie, zwrócić jej adres i rozmiar oraz upewnić się, że wywołujący uderza wskaźnik stosu aż do końca zwracanej tablicy. Być może jakiś stack ramki mocowanie zrobić, ale w żaden sposób niemożliwe.

 0
Author: Nick Strupat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-04 01:42:52