Bilinmeyen boyutta bir std :: dizisini bir işleve geçirme


104

C ++ 11'de, bilinen türde ancak bilinmeyen boyutta bir std :: dizisi alan bir işlev (veya yöntem) yazmaya nasıl başlayabilirim?

// made up example
void mulArray(std::array<int, ?>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

// lets imagine these being full of numbers
std::array<int, 17> arr1;
std::array<int, 6>  arr2;
std::array<int, 95> arr3;

mulArray(arr1, 3);
mulArray(arr2, 5);
mulArray(arr3, 2);

Aramam sırasında yalnızca şablonları kullanmak için öneriler buldum, ancak bunlar karmaşık (başlıktaki yöntem tanımları) ve başarmaya çalıştığım şey için aşırı görünüyor.

Sade C tarzı dizilerde olduğu gibi, bunu çalıştırmanın basit bir yolu var mı?


1
Dizilerin sınırları yoktur veya ne büyüklükte olduklarını bilirler. Bu nedenle, onları bir şeye sarmalı veya kullanmayı düşünmelisiniz std::vector.
Travis Pessetto

20
Şablonlar size dağınık ve aşırı görünüyorsa, bu duyguyu aşmalısınız. C ++ 'da yaygındırlar.
Benjamin Lindley

std::vector@TravisPessetto'nun önerdiği gibi kullanmamak için herhangi bir neden var mı?
Cory Klein

2
Anladım. Eğer bu onların doğasının bir sınırlamasıysa, bunu kabul etmem gerekecek. Std :: vector'den kaçınmayı düşünmemin nedeni (bu benim için harika çalışıyor), yığın üzerinde tahsis edilmiş olmasıdır. Bu diziler küçük ve programın her yinelemesinde döngü içinde olacağından, std :: dizisinin biraz daha iyi performans göstereceğini düşündüm. Sanırım C tarzı bir dizi kullanacağım, programım karmaşık değil.
Adrian

15
@Adrian Performans hakkında düşünme tarzınız tamamen yanlış. Daha işlevsel bir programınız olmadan mikro optimizasyonlar yapmaya çalışmayın. Ve bir programa sahip olduktan sonra , neyin optimize edilmesi gerektiğini tahmin etmeyin , bunun yerine bir profilcinin size programın hangi bölümünün optimize edilmesi gerektiğini söylemesine izin verin.
Paul Manta

Yanıtlar:


92

Sade C tarzı dizilerde olduğu gibi, bunu çalıştırmanın basit bir yolu var mı?

Hayır. İşlevinizi bir işlev şablonu yapmadıkça (veya std::vectorsoruya yapılan yorumlarda önerildiği gibi, bir gibi başka tür bir kap kullanmadıkça) bunu gerçekten yapamazsınız :

template<std::size_t SIZE>
void mulArray(std::array<int, SIZE>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

İşte canlı bir örnek .


9
OP, şablonlardan başka bir çözüm olup olmadığını sorar.
Novak

1
@Adrian: Maalesef başka bir çözüm yok, eğer fonksiyonunuzun herhangi bir boyuttaki dizilerde genel olarak çalışmasını istiyorsanız ...
Andy Prowl

1
Doğru: başka yolu yok. Farklı büyüklükteki her std :: dizi farklı bir tür olduğundan, farklı türlerde çalışabilecek bir işlev yazmanız gerekir. Bu nedenle, şablonlar std :: array için çözümdür.
bstamour

4
Burada bir şablon kullanmanın güzel yanı, bunu daha genel hale getirebilmenizdir, böylece herhangi bir dizi kapsayıcısı ve standart dizilerle çalışır:template<typename C, typename M> void mulArray(C & arr, M multiplier) { /* same body */ }
Benjamin Lindley

2
@AndyProwl Canlı örnek bağlantısı artık çalışmıyor
Ahmed Hussein

29

Büyüklüğü arrayolan tip bir parçası istediğiniz oldukça ne yapamaz, böylece. Birkaç alternatif var.

Tercih edilen, bir çift yineleyici almak olacaktır:

template <typename Iter>
void mulArray(Iter first, Iter last, const int multiplier) {
    for(; first != last; ++first) {
        *first *= multiplier;
    }
}

Alternatif olarak, vectorboyutu türünün bir parçası yerine çalışma zamanında depolamanıza olanak tanıyan dizi yerine kullanın :

void mulArray(std::vector<int>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

2
Bence bu üstün bir çözüm; Bir şablon oluşturma zahmetinden geçecekseniz, herhangi bir kapsayıcıyı (dizi, liste, vektör, hatta eski okul C işaretçileri, vb.) hiçbir dezavantajı olmadan kullanmanıza izin verecek yineleyicilerle tamamen genel yapın . İpucu için teşekkürler.
Mark Lakata

9

DÜZENLE

C ++ 20 geçici olarak şunları içerir: std::span

https://en.cppreference.com/w/cpp/container/span

Orijinal Cevap

İstediğiniz şey gsl::span, C ++ Temel Yönergelerinde açıklanan Kılavuz Destek Kitaplığı'nda bulunan gibi bir şeydir :

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#SS-views

GSL'nin yalnızca açık kaynak başlık içeren bir uygulamasını burada bulabilirsiniz:

https://github.com/Microsoft/GSL

İle gsl::spanbunu yapabilirsiniz:

// made up example
void mulArray(gsl::span<int>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

// lets imagine these being full of numbers
std::array<int, 17> arr1;
std::array<int, 6>  arr2;
std::array<int, 95> arr3;

mulArray(arr1, 3);
mulArray(arr2, 5);
mulArray(arr3, 2);

Sorun std::array, boyutunun türünün bir parçası olmasıdır, bu nedenle std::arrayrastgele boyutta bir işlevi uygulamak için bir şablon kullanmanız gerekir .

gsl::spandiğer yandan boyutunu çalışma zamanı bilgisi olarak saklar. Bu, rastgele boyuttaki bir diziyi kabul etmek için şablon olmayan bir işlevi kullanmanıza izin verir. Diğer bitişik kapsayıcıları da kabul eder:

std::vector<int> vec = {1, 2, 3, 4};
int carr[] = {5, 6, 7, 8};

mulArray(vec, 6);
mulArray(carr, 7);

Oldukça havalı, ha?


6

Aşağıda denedim ve sadece benim için çalıştı.

#include <iostream>
#include <array>

using namespace std;

// made up example
void mulArray(auto &arr, const int multiplier) 
{
    for(auto& e : arr) 
    {
        e *= multiplier;
    }
}

void dispArray(auto &arr)
{
    for(auto& e : arr) 
    {
        std::cout << e << " ";
    }
    std::cout << endl;
}

int main()
{

    // lets imagine these being full of numbers
    std::array<int, 7> arr1 = {1, 2, 3, 4, 5, 6, 7};
    std::array<int, 6> arr2 = {2, 4, 6, 8, 10, 12};
    std::array<int, 9> arr3 = {1, 1, 1, 1, 1, 1, 1, 1, 1};

    dispArray(arr1);
    dispArray(arr2);
    dispArray(arr3);

    mulArray(arr1, 3);
    mulArray(arr2, 5);
    mulArray(arr3, 2);

    dispArray(arr1);
    dispArray(arr2);
    dispArray(arr3);

    return 0;
}

ÇIKTI :

1 2 3 4 5 6 7

2 4 6 8 10 12

1 1 1 1 1 1 1 1 1

3 6 9 12 15 18 21

10 20 30 40 50 60

2 2 2 2 2 2 2 2 2


3
Bu geçerli C ++ değil, bir uzantıdır. Bu işlevler, olmadan bile şablonlardır template.
HolyBlackCat

1
Buna baktım ve auto foo(auto bar) { return bar * 2; }GCC7'de C ++ 17 bayrak setiyle derlemesine rağmen şu anda geçerli C ++ olmadığı görülüyor . Okuyunca burada , işlev parametreleri sonunda 20 C ++ parçası olmalıdır Kavramlar TS parçası olan oto olarak ilan etti.
Fibbles


3

Kesinlikle, C ++ 11'de bilinen türde ancak bilinmeyen boyutta bir std :: dizi alan bir işlev yazmanın basit bir yolu vardır.

Dizi boyutunu işleve geçiremezsek, bunun yerine dizinin başladığı bellek adresini dizinin bittiği yerin 2. adresi ile birlikte geçirebiliriz. Daha sonra, fonksiyonun içinde, dizinin boyutunu hesaplamak için bu 2 hafıza adresini kullanabiliriz!

#include <iostream>
#include <array>

// The function that can take a std::array of any size!
void mulArray(int* piStart, int* piLast, int multiplier){

     // Calculate the size of the array (how many values it holds)
     unsigned int uiArraySize = piLast - piStart;

     // print each value held in the array
     for (unsigned int uiCount = 0; uiCount < uiArraySize; uiCount++)     
          std::cout << *(piStart + uiCount) * multiplier << std::endl;
}

int main(){   

     // initialize an array that can can hold 5 values
     std::array<int, 5> iValues;

     iValues[0] = 5;
     iValues[1] = 10;
     iValues[2] = 1;
     iValues[3] = 2;
     iValues[4] = 4;

     // Provide a pointer to both the beginning and end addresses of 
     // the array.
     mulArray(iValues.begin(), iValues.end(), 2);

     return 0;
}

Konsolda Çıktı: 10, 20, 2, 4, 8


1

Bu yapılabilir, ancak temiz bir şekilde yapılması birkaç adım alır. İlk olarak, template classbir dizi bitişik değerleri temsil eden bir yazın. Ardından , bu bitişik aralığı alan sürüme templatene kadar büyük olduğunu bilen bir sürümü iletin .arrayImpl

Son olarak, contig_rangesürümü uygulayın. Not for( int& x: range )için çalışan contig_rangeben hayata çünkü begin()ve end()ve işaretçileri yineleyiciler bulunmaktadır.

template<typename T>
struct contig_range {
  T* _begin, _end;
  contig_range( T* b, T* e ):_begin(b), _end(e) {}
  T const* begin() const { return _begin; }
  T const* end() const { return _end; }
  T* begin() { return _begin; }
  T* end() { return _end; }
  contig_range( contig_range const& ) = default;
  contig_range( contig_range && ) = default;
  contig_range():_begin(nullptr), _end(nullptr) {}

  // maybe block `operator=`?  contig_range follows reference semantics
  // and there really isn't a run time safe `operator=` for reference semantics on
  // a range when the RHS is of unknown width...
  // I guess I could make it follow pointer semantics and rebase?  Dunno
  // this being tricky, I am tempted to =delete operator=

  template<typename T, std::size_t N>
  contig_range( std::array<T, N>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
  template<typename T, std::size_t N>
  contig_range( T(&arr)[N] ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
  template<typename T, typename A>
  contig_range( std::vector<T, A>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
};

void mulArrayImpl( contig_range<int> arr, const int multiplier );

template<std::size_t N>
void mulArray( std::array<int, N>& arr, const int multiplier ) {
  mulArrayImpl( contig_range<int>(arr), multiplier );
}

(test edilmemiştir, ancak tasarım çalışmalıdır).

Ardından .cppdosyanızda:

void mulArrayImpl(contig_range<int> rng, const int multiplier) {
  for(auto& e : rng) {
    e *= multiplier;
  }
}

Bunun dezavantajı, dizinin içeriği üzerinde döngü yapan kodun dizinin ne kadar büyük olduğunu (derleme zamanında) bilmemesidir, bu da optimizasyona mal olabilir. Uygulamanın başlıkta olması gerekmemesi avantajına sahiptir.

A'yı açıkça oluştururken dikkatli olun contig_range, sanki onu iletirseniz, verilerin bitişik setolduğunu varsayar set, bu yanlıştır ve her yerde tanımsız davranışlar sergiler. Bunun stdüzerinde çalışacağı garanti edilen tek iki kap vectorve array(ve olduğu gibi C-tarzı diziler!). dequerasgele erişim olmasına rağmen (tehlikeli olarak, küçük parçalar halinde bitişiktir!), listhatta yakın değildir ve ilişkilendirici (sıralı ve sırasız) kapsayıcılar eşit derecede bitişik değildir.

Üç kurucular Yani nerede uygulanan std::array, std::vectortemelde üsleri kapsayan ve C tarzı diziler.

Uygulama []hem kolaydır ve aralarında for()ve []bir istediğiniz bu çoğu ise array, değil mi için?


Bu sadece şablonu başka bir yere kaydırmıyor mu?
GManNickG

@GManNickG tür. Başlık template, hemen hemen hiçbir uygulama detayı olmayan gerçekten kısa bir işlev alır . Implİşlev bir değil templateişlevi ve böylece mutlu içinde gizleyebilir bir uygulama .cppseçtiğiniz dosyanın. Bu, bitişik kapları daha basit bir sınıfa yineleme yeteneğini çıkardığım ve sonra bunu geçtiğim gerçekten kaba bir tür silme türüdür ... (a'yı bir argüman olarak alırken multArrayImpl, kendisi templatebir değil template).
Yakk - Adam Nevraumont

Bu dizi görünümü / dizi proxy sınıfının bazen yararlı olduğunu anlıyorum. Benim önerim, yapıcıdaki kabın başlangıcını / sonunu iletmektir, böylece her kap için bir kurucu yazmak zorunda kalmazsınız. Ayrıca '& * std :: begin (arr)' yazmazdım, çünkü burada referans alma gereksizdir ve std :: begin / std :: end zaten bir yineleyici döndürdüğünden burada gereksizdir.
Ricky65

@ Ricky65 Yineleyiciler kullanıyorsanız, uygulamayı ifşa etmeniz gerekir. İşaretçiler kullanırsanız, kullanmazsınız. &*Dereferences (bir işaretçi olabilir) yineleyici, daha sonra adresine bir işareti sağlar. Bitişik bellek verileri için, beginişaretçi ve bir geçmişe işaretçi endde rastgele erişim yineleyicileridir ve bir tür üzerindeki her bitişik aralık için aynı türdedir T.
Yakk - Adam Nevraumont
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.