besserer Code-Stil für arrays



  • Hey,
    ich habe m * n int Werte und möchte für jedes m mit den dazugehörigen n Werten
    etwas machen. Wie coded man das gewöhnlich?

    Wo setzt man die * hin? (alles das selbe)

    int **mat = new int*[m];
       int** mat = new int*[m];
       int ** mat = new int*[m];
    
       int **mat2, **mat3; //so muss es ja bei mehreren sein
       mat2 = new int*[m]; //new int* liefert aber den Wert für einen int** und setzt den Wert für mat2 und nicht **mat2;
    

    dann

    for(int i = 0; i < m; i++) mat[m] = new int*[n];
    

    oder lieber gleich

    int *mat_mn = new int*[m*n];
    

    und wenn dann eine Funktion für ein mi (0..(m-1)) etwas tun soll:

    int *mat_mi = &mat[mi]; // oder
       int *mat_mi = mat+mi;
       int *mat_mi = mat_mn + n * mi;
    

    Ein 'warum' man es so macht wäre auch gut.



  • ups das letzte war falsch, so sollte es sein

    int *mat_mi = &mat[mi][0];
       int *mat_mi = mat[mi]; // oder
       int *mat_mi = mat+mi;
       int *mat_mi = mat_mn + n * mi;
       int *mat_mi = &mat_mn[n * mi];
    


  • In C++ macht man

    const int WIDTH = ..., HEIGHT = ...;
    std::array<std::array<int, WIDTH>, HEIGHT> array2D; //feste Größe
    array2D[1][2] = ...;
    //bzw. wenns erweiterbar sein soll
    std::vector<std::vector<int>> array2D;
    array2D.resize(HEIGHT);
    array2D[0].push_back(...);
    

    Siehe http://de.cppreference.com/w/cpp/container/array und http://de.cppreference.com/w/cpp/container/vector
    Mit rohen Zeigern rumhantieren ist aufgrund der schlechteren Handhabung und des Risikos von Speicherlecks bei manueller Speicherverwaltung eine schlechte Idee.



  • Wie wäre es mit

    std::vector<std::vector<int>> v(m, std::vector<int>(n));
    

    Oftmals ist es sinnvoll, das ganze gleich in einem m*n-Feld zu machen, dann:

    std::vector<int> v(m * n);
    //oder:
    auto u = std::make_unique<int[]>(m * n);
    

    Ich würde dir von all deinen Varianten abraten. Wozu Speicher selbst managen?

    Und zur 2. Frage (Übergabe an Funktion): warum nicht, wie in der STL üblich, einfach ein entsprechendes Iteratorpaar?



  • Am besten eine kleine Wrapper-Klasse schreiben:

    class Matrix
    {
    public:
            Matrix(size_t m, size_t n)
                    : _mat(m * n), _m(m), _n(n)
            { }
    
            int &operator()(size_t i, size_t j) { return _mat[i * _n + j]; }
            const int &operator()(size_t i, size_t j) const { return _mat[i * _n + j]; }
    
            size_t m() const { return _m; }
            size_t n() const { return _n; }
    private:
            std::vector<int> _mat;
            size_t _m;
            size_t _n;
    };
    
    int main()
    {
            Matrix m(3, 2);
            m(0, 1) = 3;
            m(2, 0) = 9;
    
            for ( size_t i = 0; i < m.m(); ++i )
            {
                    for ( size_t j = 0; j < m.n(); ++j )
                            std::cout << m(i, j) << ' ';
                    std::cout << '\n';
            }
    
    }
    

    Natuerlich schreibt man am besten gleich eine Template-Klasse.

    Grundsaetzlich ist der Zugriff auf std::vector<int> v(m * n); effizienter als bei std::vector<int> v(m, std::vector<int>(n)); . Ersteres ist aber muehsam, da man den Index immer manuell berechnen muss. Deshalb eine kleine Wrapper-Klasse.



  • Incocnito schrieb:

    Mit rohen Zeigern rumhantieren ist aufgrund der schlechteren Handhabung und des Risikos von Speicherlecks bei manueller Speicherverwaltung eine schlechte Idee.

    Und macht das effizienztechnisch keinen Unterschied?

    wob schrieb:

    Und zur 2. Frage (Übergabe an Funktion): warum nicht, wie in der STL üblich, einfach ein entsprechendes Iteratorpaar?

    Eine Funktion einer Klasse wird oft aufgerufen. Die Variable (mat) dient nur als interner Zwischenspeicher der Klasse. Von außerhalb weiß man nicht unbedingt wo der Zugriff ist. Die Klasse hat auch mehrere davon. Man müsste dann 10 iteratoren übergeben.

    icarus2 schrieb:

    Grundsaetzlich ist der Zugriff auf std::vector<int> v(m * n); effizienter als bei std::vector<int> v(m, std::vector<int>(n)); . Ersteres ist aber muehsam, da man den Index immer manuell berechnen muss.

    Sicher? Man muss doch dann immer eine Multiplikation ausführen. v[m] wäre nur einmal lesen.



  • Bei nem array fester Größe kann man wie gesagt std::array nehmen, ein kleines Anwendungsbeispiel sähe dann so aus

    #include <iostream>
    #include <array>
    
    template<std::size_t HEIGHT, std::size_t WIDTH>
    void f(std::array<std::array<int, WIDTH>, HEIGHT>& a)
    {
    	for(std::size_t i = 0; i < HEIGHT; ++i)
    	{
    		for(std::size_t j = 0; j < WIDTH; ++j)
    			a[i][j] = i;
    	}
    }
    
    int main()
    {
    	const int WIDTH = 4, HEIGHT = 3;
        std::array<std::array<int, WIDTH>, HEIGHT> array;
    	f(array);
    	for(auto iter = array.begin(); iter != array.end(); ++iter)
    	{
    		for(auto innerIter = iter->begin(); innerIter != iter->end(); ++innerIter)
    			std::cout << *innerIter << " ";
    		std::cout << "\n";
    	}
    }
    

    **Styler schrieb:

    Incocnito schrieb:

    Mit rohen Zeigern rumhantieren ist aufgrund der schlechteren Handhabung und des Risikos von Speicherlecks bei manueller Speicherverwaltung eine schlechte Idee.

    Und macht das effizienztechnisch keinen Unterschied?

    icarus2 schrieb:

    Grundsaetzlich ist der Zugriff auf std::vector<int> v(m * n); effizienter als bei std::vector<int> v(m, std::vector<int>(n)); . Ersteres ist aber muehsam, da man den Index immer manuell berechnen muss.

    Sicher? Man muss doch dann immer eine Multiplikation ausführen. v[m] wäre nur einmal lesen.

    In Zeiten von 2 Ghz Prozessoren und 1,3 Ghz RAM braucht man sich wohl kaum darum Gedanken machen, ob der vector jetzt ein paar Mikrosekunden langsamer ist. Klar hast du durch das Drumherum ein bisschen mehr Overhead, aber bekommst halt die ganze Palette an STL-Annehmlichkeiten, die den Performanceverlust meistens mehr als wettmachen. Und beim std::array hast du besagte Annehmlichkeiten und praktisch keinen Overhead, da std::array per Definition nur ein ganz dünner Wrapper um einen C-Array ist.


  • Mod

    icarus2 schrieb:

    Am besten eine kleine Wrapper-Klasse schreiben:

    Oder gleich fertige Produkte verwenden, wie etwa Boost.MultiArray


  • Mod

    **Styler schrieb:

    icarus2 schrieb:

    Grundsaetzlich ist der Zugriff auf std::vector<int> v(m * n); effizienter als bei std::vector<int> v(m, std::vector<int>(n)); . Ersteres ist aber muehsam, da man den Index immer manuell berechnen muss.

    Sicher? Man muss doch dann immer eine Multiplikation ausführen. v[m] wäre nur einmal lesen.

    Nichts hindert dich, einmalig

    auto p = &v(m,0);
    

    zu ermitteln. Von immer Multiplizieren müssen kann keine Rede sein. Das ist nur dann erforderlich, wenn Zugriffe zufällig erfolgen. Im analogen Fall sind das immer zwei Speicherzugriffe mit dem anderen Speichermodell.



  • camper schrieb:

    Nichts hindert dich, einmalig

    auto p = &v(m,0);
    

    zu ermitteln.

    Doch mein Compiler hindert mich 🙂

    int m=10, n=10;
     //std::vector<int> v(m, std::vector<int>(n)) sollte denke ich so sein:
     std::vector<std::vector<int>> v(m, std::vector<int>(n))
     auto p = &v(m,0); //-> error: no match for call to ‘(std::vector<std::vector<int> >) (int, int)’
     auto p = &v[m,0]; // das geht aber
    
    /////////////////////////////
      std::vector<int> v(m * n);
      int i=0;
      for(std::vector<int>::iterator g = v.begin(); g != v.end(); ++g)   
         *g=i++;
      auto p = &v[m,0]; //*p ist 0
      auto p = &v[m,1]; //*p ist 1
      auto p = &v[m,2]; //*p ist 2
      auto p = &v[42,2]; //*p ist 2
    

    Was hat die erste Stelle zu bedeuten?


  • Mod

    noStyler schrieb:

    camper schrieb:

    Nichts hindert dich, einmalig

    auto p = &v(m,0);
    

    zu ermitteln.

    Doch mein Compiler hindert mich 🙂

    std::vector<std::vector<int>> v(m, std::vector<int>(n))
     auto p = &v(m,0); //-> error: no match for call to ‘(std::vector<std::vector<int> >) (int, int)’
    

    Von dieser Kombination war offenkundig nicht die Rede.



  • Tut mir Leid, von welcher dann?

    std::vector<int> v(m * n)
    auto p = &v(m,0);
    

    hatte ich auch probiert und ging auch nicht.


  • Mod

    noStyler schrieb:

    Tut mir Leid, von welcher dann?

    std::vector<int> v(m * n)
    auto p = &v(m,0);
    

    hatte ich auch probiert und ging auch nicht.

    Für einen std::vector<int> v(m * n); erfolgt der Zugriff auf ein Element (x,y) durch

    v[x*n+y]
    

    bzw.

    m(x,y)
    

    mit icarus2s Matrixklasse.
    Will man nun z.B. über die Elemente einer Zeile x iterieren, ist es nicht erforderlich, ständig zu multiplizieren

    for (int y = 0; y < n; ++y)
        v[x*n+y]
    

    sondern:

    auto p = &v[x*n]; // &m(x,0) mit Matrixklasse
    for (int y = 0; y < n; ++y)
        p[y]
    

  • Mod

    Oder noch eleganter, da es zu dem üblichen Vorgehen bei Containern passt und der Benutzer daher völlig agnostisch bezüglich der inneren Struktur sein darf: Biete das was camper gezeigt hat als Iterator an.



  • Danke für die Antworten.
    Habe es vielleicht nicht richtig geschrieben.

    camper schrieb:

    auto p = &v[x*n]; // &m(x,0) mit Matrixklasse
    for (int y = 0; y < n; ++y)
        p[y]
    

    dass würde dann wiederum sehr oft aufgerufen und da meinte ich müsste dann oft, d.h. bei jedem Aufruf neu multipliziert werden.Wäre dann dann nicht 2D besser (v vektor von vektoren)? also:

    auto p = &v[x];
    


  • Wenn du Matrizen mit fester Größe hast, dh, std::array verwendest, formt der Compiler ganz sicher die Multiplikationen zu simplen shiftoperationen um.


Anmelden zum Antworten