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?

R. Martinho Fernandes
źródło

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.

James Kanze
źródło
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.”.

Szczeniak
źródło
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.

Sumant
źródło
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

Ricky65
źródło
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.

cmaster - przywróć monica
źródło
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

K'Prime
źródło
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ść
Lou Franco
źródło
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