it-swarm.com.de

Übergabe eines std :: -Arrays unbekannter Größe an eine Funktion

Wie würde ich in C++ 11 vorgehen, um eine Funktion (oder Methode) zu schreiben, die ein std :: -Array mit bekanntem Typ und unbekannter Größe verwendet?

// 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);

Während meiner Suche habe ich nur Vorschläge zur Verwendung von Vorlagen gefunden, aber diese erscheinen unsauber (Methodendefinitionen in der Kopfzeile) und übertrieben für das, was ich erreichen möchte.

Gibt es eine einfache Möglichkeit, diese Arbeit zu machen, wie dies bei einfachen Arrays im C-Stil der Fall wäre?

76
Adrian

Gibt es eine einfache Möglichkeit, diese Arbeit zu machen, wie dies bei einfachen Arrays im C-Stil der Fall wäre?

Nein. Sie können das wirklich nicht tun, wenn Sie Ihre Funktion nicht zu einer Funktion machen template (oder eine andere Art von Container verwenden, wie ein std::vector, wie in den Kommentaren zur Frage vorgeschlagen):

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

Hier ist ein Live-Beispiel .

70
Andy Prowl

Die Größe von array ist Teil des Typs, sodass Sie nicht genau das tun können, was Sie wollen. Es gibt ein paar Alternativen.

Es wäre vorzuziehen, zwei Iteratoren zu verwenden:

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

Verwenden Sie alternativ vector anstelle von array, um die Größe zur Laufzeit zu speichern, anstatt als Teil des Typs:

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

Ich habe es unten versucht und es hat nur bei mir funktioniert.

#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;
}

AUSGABE :

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

4
musk's

Absolut, es gibt in C++ 11 eine einfache Möglichkeit, eine Funktion zu schreiben, die ein std :: -Array von bekanntem Typ, aber unbekannter Größe verwendet.

Wenn wir die Arraygröße nicht an die Funktion übergeben können, können wir stattdessen die Speicheradresse, an der das Array beginnt, zusammen mit einer zweiten Adresse, an der das Array endet, übergeben. Später, innerhalb der Funktion, können wir diese 2 Speicheradressen verwenden, um die Größe des Arrays zu berechnen!

#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;
}

Ausgabe an Konsole: 10, 20, 2, 4, 8

2

[~ # ~] edit [~ # ~]

C++ 20 enthält vorläufig std::span

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

Ursprüngliche Antwort

Was Sie wollen, ist so etwas wie gsl::span, verfügbar in der Guideline Support Library, beschrieben in den C++ Core Guidelines:

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

Eine Open-Source-Implementierung der GSL nur für Header finden Sie hier:

https://github.com/Microsoft/GSL

Mit gsl::span, du kannst das:

// 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);

Das Problem mit std::array ist, dass seine Größe Teil seines Typs ist, daher müssten Sie eine Vorlage verwenden, um eine Funktion zu implementieren, die ein std::array beliebiger Größe.

gsl::span dagegen speichert seine Größe als Laufzeitinformation. Auf diese Weise können Sie eine Nicht-Vorlagenfunktion verwenden, um ein Array beliebiger Größe zu akzeptieren. Es werden auch andere zusammenhängende Container akzeptiert:

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

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

Ziemlich cool, oder?

2
suncho

Dies kann durchgeführt werden, es sind jedoch einige Schritte erforderlich, um eine saubere Ausführung zu erzielen. Schreiben Sie zunächst ein template class, Das einen Bereich zusammenhängender Werte darstellt. Leiten Sie dann eine template Version weiter, die weiß, wie groß die array ist, zu der Impl Version, die diesen zusammenhängenden Bereich einnimmt.

Implementieren Sie abschließend die Version contig_range. Beachten Sie, dass for( int& x: range ) für contig_range Funktioniert, da ich begin() und end() implementiert habe und Zeiger Iteratoren sind.

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 );
}

(nicht getestet, aber das Design sollte funktionieren).

Dann in Ihrer .cpp - Datei:

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

Dies hat den Nachteil, dass der Code, der den Inhalt des Arrays durchläuft, (zur Kompilierungszeit) nicht weiß, wie groß das Array ist, was eine Kostenoptimierung bedeuten könnte. Dies hat den Vorteil, dass sich die Implementierung nicht im Header befinden muss.

Seien Sie vorsichtig beim expliziten Konstruieren eines contig_range, Als ob Sie ihm eine set übergeben würden. Dabei wird davon ausgegangen, dass die set -Daten zusammenhängend sind, was falsch ist und undefiniertes Verhalten bewirken überall. Die einzigen zwei std Container, an denen dies garantiert funktioniert, sind vector und array (und Arrays im C-Stil, wie es passiert!). deque ist nicht zusammenhängend (gefährlich, in kleinen Blöcken zusammenhängend!), list ist nicht einmal eng, und die assoziativen (geordneten und ungeordneten) Container sind gleich nicht zusammenhängend.

Die drei Konstruktoren, die ich implementiert habe, waren std::array, std::vector Und Arrays im C-Stil, die im Wesentlichen die Basen abdecken.

Das Implementieren von [] Ist ebenfalls einfach, und zwischen for() und [] Wollen Sie zumeist eine array, nicht wahr?