Teraz, gdy mamy std :: array, jakie zastosowania pozostały dla tablic w stylu C?


88

std::arrayznacznie przewyższa tablice C. A nawet jeśli chcę współdziałać ze starszym kodem, mogę po prostu użyć std::array::data(). Czy jest jakiś powód, dla którego chciałbym kiedykolwiek mieć tablicę ze starej szkoły?

Odpowiedzi:


60

O ile czegoś nie przeoczyłem (nie śledziłem zbyt uważnie ostatnich zmian w standardzie), większość zastosowań tablic w stylu C nadal pozostaje. std::arraypozwala na statyczną inicjalizację, ale nadal nie będzie liczyć inicjatorów za Ciebie. A ponieważ jedyne rzeczywiste użycie tablic w stylu C wcześniej std::arraydotyczyło statycznie inicjowanych tabel w następujący sposób:

MyStruct const table[] =
{
    { something1, otherthing1 },
    //  ...
};

używając funkcji zwykłych begini endszablonów (przyjętych w C ++ 11) do iteracji po nich. Nie wspominając o rozmiarze, który kompilator określa na podstawie liczby inicjatorów.

EDYCJA: Kolejna rzecz, o której zapomniałem: literały łańcuchowe są nadal tablicami w stylu C; tj. z typem char[]. Nie sądzę, aby ktokolwiek wykluczył używanie literałów ciągów tylko dlatego, że tak jest std::array.


7
Możesz napisać wariadyczny szablon funkcji, który konstruuje tablice bez konieczności określania długości.
— prawy bok

2
W C ++ 17 Class Template Deduction obsługiwane jest automatyczne odejmowanie liczby inicjatorów. Na przykład "auto a = std :: array {1, 2, 3};"
— Ricky65

Nitpick: typ literałów strunowych toconst char[]
— Bulletmagnet,

30

Nie, żeby, uh, ująć to bez ogródek. I w 30 znakach.

Oczywiście do zaimplementowania potrzebne są tablice C std::array, ale nie jest to tak naprawdę powód, dla którego użytkownik kiedykolwiek chciałby mieć tablice C. Ponadto nie, std::arraynie jest mniej wydajna niż tablica C i ma opcję dostępu z kontrolą ograniczeń. I wreszcie, jest całkowicie uzasadnione, aby jakikolwiek program w C ++ zależał od biblioteki standardowej - w pewnym sensie chodzi o to, że jest to standardowa - i jeśli nie masz dostępu do biblioteki standardowej, to twój kompilator jest niezgodny i pytanie jest oznaczone jako „C ++”, a nie „C ++ i te rzeczy nie-C ++, które pomijają połowę specyfikacji, ponieważ uważają, że jest nieodpowiednie.”.


1
Hm. A co jeśli piszesz kod w C ++, który jest wywoływany z innego języka i potrzebuje czegoś do przekazania jako parametru?
— asveikau

3
Wolnostojące implementacje mogą pominąć prawie całą standardową bibliotekę i nadal być zgodne. Miałbym jednak poważne wątpliwości co do kompilatora, którego nie można zaimplementować std::arrayw wolnostojącej implementacji C ++ 11.
— Dennis Zickefoose

11
C ++ 0x Wersja ostateczna (dokument N3092) § 1.4.7 „Zdefiniowano dwa rodzaje implementacji: hostowaną i wolnostojącą. W przypadku implementacji hostowanej niniejszy standard międzynarodowy definiuje zestaw dostępnych bibliotek. Implementacja wolnostojąca to taka, w której wykonanie może odbywa się bez korzyści systemu operacyjnego i ma zestaw bibliotek zdefiniowany w ramach implementacji, który obejmuje pewne biblioteki obsługujące języki ".. STL nie jest dołączana jako biblioteka" obsługująca języki "w niezależnym kompilatorze
— Earlz

24

Wydaje się, że używanie tablic wielowymiarowych jest łatwiejsze w przypadku tablic w języku C niż std::array. Na przykład,

char c_arr[5][6][7];

w przeciwieństwie do

std::array<std::array<std::array<char, 7>, 6>, 5> cpp_arr;

Również ze względu na właściwość automatycznego rozpadu tablic C, c_arr[i]w powyższym przykładzie rozpadnie się do wskaźnika i wystarczy przekazać pozostałe wymiary jako dwa dodatkowe parametry. Chodzi mi o to, że c_arrkopiowanie nie jest drogie. Jednak cpp_arr[i]kopiowanie będzie bardzo kosztowne.


1
Możesz jednak przekazać funkcję wielowymiarową arraybez utraty wymiarów. A jeśli przekażesz to do szablonu funkcji, funkcja ta może wydedukować zarówno wymiar, jak i rozmiar każdego wymiaru lub tylko jednego z nich dwóch. Może to być interesujące dla naukowych bibliotek szablonów, które pracują głównie na dowolnych wymiarach.
— Sebastian Mach,

29
prosta template <typename T, int M, int N> using array2d = std::array<std::array<T, N>, M>;powinna rozwiązać każdy z tych problemów.
— Miles Rout

6
Twój przykład c_arrjest bardzo drogi do skopiowania! Aby to zrobić, musisz podać kod. Wskaźnik, na który się rozpadnie, jest bliższy referencji niż kopia i możesz użyć go std::arraydo przekazania referencji, jeśli tego chcesz.
— ChetS

1
@MilesRout, technicznie rzecz biorąc , nie powinno być std::size_tzamiast tego int? przepraszam za czepianie się, ale to uczyniłoby to uniwersalnym.
— robbie,

1
@ robbie0630 Tak, możesz to zrobić, size_tjeśli chcesz, chociaż nie wyobrażam sobie, że istnieje wiele scenariuszy, w których potrzebne są tablice zawierające więcej niż 4 miliardy wierszy lub kolumn.
— Miles Rout,

13

Jak powiedział Sumant, wielowymiarowe tablice są o wiele łatwiejsze w użyciu z wbudowanymi tablicami C niż z std::array.

Po zagnieżdżeniu std::arraymoże stać się bardzo trudny do odczytania i niepotrzebnie rozwlekły.

Na przykład:

std::array<std::array<int, 3>, 3> arr1; 

w porównaniu do

char c_arr[3][3]; 

Należy również pamiętać, że begin(), end()a size()wszystkie wartości bezsensowne powrotne gdy gniazdo std::array.

Z tych powodów utworzyłem własne wielowymiarowe kontenery tablic o stałym rozmiarze array_2di array_3d. Są analogiczne do std::arraywielowymiarowych tablic 2 i 3 wymiarów, ale. Są bezpieczniejsze i nie mają gorszej wydajności niż wbudowane tablice wielowymiarowe. Nie załączyłem kontenera na wielowymiarowe tablice o wymiarach większych niż 3, ponieważ są one rzadkie. W C ++ 0x można by stworzyć wariadyczną wersję szablonu, która obsługuje dowolną liczbę wymiarów.

Przykład wariantu dwuwymiarowego:

//Create an array 3 x 5 (Notice the extra pair of braces) 

fsma::array_2d <double, 3, 5> my2darr = {{
    { 32.19, 47.29, 31.99, 19.11, 11.19},
    { 11.29, 22.49, 33.47, 17.29, 5.01 },
    { 41.97, 22.09, 9.76, 22.55, 6.22 }
}};

Pełna dokumentacja jest dostępna tutaj:

http://fsma.googlecode.com/files/fsma.html

Możesz pobrać bibliotekę tutaj:

http://fsma.googlecode.com/files/fsma.zip


4
Tablice o stałym rozmiarze w stylu C są łatwe, ale jeśli chcesz zmieniać wymiary, sprawy się komplikują. Na przykład, biorąc pod uwagę arr[x][y], nie możesz stwierdzić, czy arrjest to tablica tablic, tablica wskaźników, wskaźnik do tablicy, czy wskaźnik do wskaźnika; wszystkie do realizacji są zgodne z prawem, w zależności od Twoich potrzeb. I prawdopodobnie większość rzeczywistych przypadków użycia tablic wielowymiarowych wymaga określenia rozmiaru w czasie wykonywania.
— Keith Thompson,

Bardzo chciałbym zobaczyć, jak implementacja wielowymiarowych szablonów tablic n-wymiarowych! Metaprogramowanie w najlepszym wydaniu!
— steffen

3
@steffen Podjąłem próbę kilka lat temu. Możesz go zobaczyć tutaj: code.google.com/p/fsma/source/browse/trunk/… . Przypadek testowy tutaj: code.google.com/p/fsma/source/browse/trunk/… . Jestem pewien, że można to zrobić o wiele lepiej.
— Ricky65

5

Tablice w stylu C, które są dostępne w C ++, są w rzeczywistości znacznie mniej wszechstronne niż rzeczywiste tablice C. Różnica polega na tym, że w języku C typy tablic mogą mieć rozmiary w czasie wykonywania . Poniższy kod jest prawidłowym kodem w C, ale nie można go wyrazić za pomocą tablic w stylu C ++ C ani za pomocą array<>typów C ++ :

void foo(int bar) {
    double tempArray[bar];
    //Do something with the bar elements in tempArray.
}

W C ++ musiałbyś alokować tymczasową tablicę na stercie:

void foo(int bar) {
    double* tempArray = new double[bar];
    //Do something with the bar elements behind tempArray.
    delete[] tempArray;
}

Nie można tego osiągnąć za pomocą std::array<> , ponieważ barnie jest znana w czasie kompilacji, wymaga użycia tablic w stylu C w C ++ lub z std::vector<>.


Podczas gdy pierwszy przykład można stosunkowo łatwo wyrazić w C ++ (aczkolwiek wymaga new[]i delete[]), następujące nie mogą zostać osiągnięte w C ++ bezstd::vector<> :

void smoothImage(int width, int height, int (*pixels)[width]) {
    int (*copy)[width] = malloc(height*sizeof(*copy));
    memcpy(copy, pixels, height*sizeof(*copy));
    for(y = height; y--; ) {
        for(x = width; x--; ) {
            pixels[y][x] = //compute smoothed value based on data around copy[y][x]
        }
    }
    free(copy);
}

Chodzi o to, że wskaźniki do tablic liniowych int (*)[width] nie mogą używać szerokości czasu wykonywania w C ++, co sprawia, że ​​jakikolwiek kod do manipulacji obrazami jest znacznie bardziej skomplikowany w C ++ niż w C. Typowa implementacja C ++ przykładu manipulacji obrazem wyglądałaby tak:

void smoothImage(int width, int height, int* pixels) {
    int* copy = new int[height*width];
    memcpy(copy, pixels, height*width*sizeof(*copy));
    for(y = height; y--; ) {
        for(x = width; x--; ) {
            pixels[y*width + x] = //compute smoothed value based on data around copy[y*width + x]
        }
    }
    delete[] copy;
}

Ten kod wykonuje dokładnie te same obliczenia, co kod C powyżej, ale musi wykonać ręczne obliczenia indeksu gdziekolwiek indeksy są używane . W przypadku 2D jest to nadal wykonalne (mimo że wiąże się z wieloma możliwościami błędnego obliczenia wskaźnika). Jednak w przypadku 3D robi się naprawdę paskudnie.

Lubię pisać kod w C ++. Ale ilekroć muszę manipulować wielowymiarowymi danymi, naprawdę zadaję sobie pytanie, czy powinienem przenieść tę część kodu do C.


7
Należy zauważyć, że przynajmniej Clang i GCC obsługują VLA w C ++.
— Janus Troelsen

@JanusTroelsen, a także to, że mają straszne ograniczenia w zakresie obsługiwanych typów elementów.
— prawostronny

Czy C11 nie czyni VLA opcjonalnym? Jeśli tak, to myślę, że twoja odpowiedź jest myląca. Byłoby dobrze, gdyby C99 był standardem, ale nie C11.
— Bozon Z

1
@Zboson C99 to standard C i istnieją kompilatory, które implementują jego funkcje VLA ( gccna przykład). C11 wprowadził całkiem sporo interesujących rzeczy opcjonalnych i nie sądzę, żeby to dlatego, że chcą zakazać tej funkcji. Zwykle postrzegam to jako znak, że chcieli obniżyć poziom pisania w pełni zgodnego ze standardami kompilatora: VLA to dość trudna bestia do zaimplementowania, a wiele kodu może się bez niego obejść, więc nowy kompilator ma sens na jakimś nowym platformy, aby nie musieć od razu wdrażać VLA.
— cmaster

-1

Może std::arrayto nie jest powolne. Ale wykonałem trochę testów porównawczych przy użyciu prostego magazynu i przeczytałem ze std :: array; Zobacz poniższe wyniki testów porównawczych (na W8.1, VS2013 Update 4):

ARR_SIZE: 100 * 1000
Avrg = Tick / ARR_SIZE;

test_arr_without_init
==>VMem: 5.15Mb
==>PMem: 8.94Mb
==>Tick: 3132
==>Avrg: 0.03132
test_arr_with_init_array_at
==>VMem: 5.16Mb
==>PMem: 8.98Mb
==>Tick: 925
==>Avrg: 0.00925
test_arr_with_array_at
==>VMem: 5.16Mb
==>PMem: 8.97Mb
==>Tick: 769
==>Avrg: 0.00769
test_c_arr_without_init
==>VMem: 5.16Mb
==>PMem: 8.94Mb
==>Tick: 358
==>Avrg: 0.00358
test_c_arr_with_init
==>VMem: 5.16Mb
==>PMem: 8.94Mb
==>Tick: 305
==>Avrg: 0.00305

Zgodnie z negatywnymi znakami kod, którego użyłem, znajduje się w pastebinie ( link )

Kod klasy testu porównawczego jest tutaj ;

Nie wiem zbyt wiele o testach porównawczych ... Mój kod może być wadliwy


6
Wyniki testów porównawczych bez kodu testu porównawczego lub flag kompilacji? Chodź, możesz zrobić lepiej.
— R. Martinho Fernandes

FWIW, tylko ten mały fragment kodu już pokazuje, że benchmark jest poważnie wadliwy. Wystarczająco inteligentny kompilator po prostu zmieni wszystko wlong test_arr_without_init() { return ARR_SIZE; }
— R. Martinho Fernandes

To był tylko przykład. Myślałem, że to nic wielkiego. Zmieniłem kod, aby zwracał void, użyłem kompilacji wydania w VS 2013 z / O2 / Ot / Gl.
— K'Prime

Usunięcie wartości zwracanej oznacza, że ​​kompilator może przekształcić całość w void test_arr_without_init() {}teraz. Naprawdę musisz przeskakiwać przez obręcze, aby upewnić się, że kod, który mierzysz, jest kodem, który chcesz zmierzyć.
— R. Martinho Fernandes

-6
  1. zaimplementować coś takiego std::array
  2. jeśli nie chcesz używać STL lub nie możesz
  3. Wydajność

27
Powiedz mi, jak std::arraybędzie mniej wydajna niż tablica C.
— Xeo

2
Z Wikipedii : „Implementacja tablicy nie jest wymagana do sprawdzania związanego. Jednak implementacja w trybie boost robi to dla operatora [], ale nie dla iteratorów”. - więc operator [] jest wolniejszy. Nie oglądałem implementacji, ale jakikolwiek kod w implementacji mógłby przeszkodzić optymalizatorowi.
— Lou Franco

19
@Aaron McDaid: To jest tylko w środku at(), nie ma go operator[], tak jak std::vector. Nie ma spadku wydajności ani nadmiaru kodu std::array, kompilator został zaprojektowany do optymalizacji tego rodzaju rzeczy. I oczywiście dodanie sprawdzonej funkcji jest doskonałym narzędziem do debugowania i dużą zaletą. @Lou Franco: Cały kod C ++ może zależeć od biblioteki Standard - do tego właśnie służy. @Earlz: Jeśli nie masz dostępnego STL, to nie jest to C ++ i na tym koniec.
— Puppy

6
@Earlz: C ++ Standard zawiera bibliotekę Standard. Jeśli nie możesz korzystać z biblioteki, oznacza to, że jest niezgodna. Po drugie, musisz mieć cholernie gówniany kompilator, aby jego użycie std::arraybyło większe niż równoważne użycie tablicy C.
— Puppy

5
@Earlz: Istnieje duża różnica między „niezupełnie zgodnymi” a „brakującymi funkcjami, które obejmują setki stron w specyfikacji”.
— Puppy
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.