... must be a nonstatic member function.



  • Hi, ich habe hier einen Fehler bei dem ich einfach nicht weiterkomme:

    Class Bla
    {
    
    //... 
    
    float *operator [] ( unsigned int i );
    
    }
    
    float Matrix:: *operator [] ( unsigned int i ){}
    

    Ich bekomme hier die Fehlermeldung

    " 'float Matrix:: *operator [] ( unsigned int i )' must be a nonstatic member function"

    Habt bitte Erbarmen mit mir, wenn dieser Fehler allzu dämlich ist, programmiere erst seit gestern 😉



  • Wozu ist das * bei Matrix:: *operator [] gut?
    BTW: Gerade die ersten Wochen sollte man probieren, selbstständig zu lernen. Alles, was man in diesem Stadium zu wissen braucht steht in dem Buch, das man hat (sofern es nicht von JW ist).



  • Sorry! Der Stern fehlte einfach weil ich ihn vergessen hatte. Ein Buch ist übrigens schon bestellt, ich muss diese Aufgabe blos für nächste Woche irgendwie fertig bekommen.



  • float* Matrix::operator[] ( unsigned int i ){}
    


  • Hey, danke erstmal!

    Hätte noch eine Frage dazu: Wie kann ich eigentlich den Operator so verändern, dass ich auch zweidimensional arbeiten kann, also sozusagen auch nach a[][] verlangen kann. Ich arbeite zurzeit mit einem eindimensionalen Array, dass eigentlich eine Matrix ist. Daher wäre so eine zweidimensionale Ansicht schon sehr gut. Und nein, ich darf leider nicht mit Vector arbeiten 😉



  • Unsaubere Variante:

    class Matrix {
    public:
      //...
            float* operator[](size_t x)       { return data+x*width; }
      const float* operator[](size_t x) const { return data+x*width; }
    private:
      size_t height, width;
      float *data;
    }
    

    Saubere Variante:

    class Matrix {
    public:
      //...
      class Proxy {
        explicit Proxy(float *data)
              float  operator[](size_t y)        { return data[y]; }
        const float& operator[](size_t y)  const { return data[y]; }
      private:
        float *data;
      };
            Proxy operator[](size_t x) { return Proxy(data+x*height); }
      const Proxy operator[](size_t x) { return Proxy(data+x*height); }
    private:
      size_t height, width;
      float *data;
    }
    


  • Hi,

    funktioniert sehr gut! Habe die schlechtere Variante genommen 😉 Könntest du mir vielleicht noch kurz in wenigen Worten erklären was da vor sich geht? Warum kann ich bereits in der eigentlichen Deklaration den Operator umdefinieren? Und was passiert bei

    " const float* operator[](unsigned int x) const { return matrix+x*columns; } "

    überhaupt genau? (Hab's angepasst)
    Vielen Dank für die gute Hilfe!



  • Dilkad schrieb:

    Unsaubere Variante:

    class Matrix {
    public:
      //...
            float* operator[](size_t x)       { return data+x*width; }
      const float* operator[](size_t x) const { return data+x*width; }
    private:
      size_t height, width;
      float *data;
    }
    

    Saubere Variante:

    class Matrix {
    public:
      //...
      class Proxy {
        explicit Proxy(float *data)
              float  operator[](size_t y)        { return data[y]; }
        const float& operator[](size_t y)  const { return data[y]; }
      private:
        float *data;
      };
            Proxy operator[](size_t x) { return Proxy(data+x*height); }
      const Proxy operator[](size_t x) { return Proxy(data+x*height); }
    private:
      size_t height, width;
      float *data;
    }
    

    Ich würde eher sowas nehmen:

    Proxy Array::op[](int y){
      return Proxy(this,y);
    }
    ...
    double& Array::Proxy::op[](int x){
       return m_array->at(m_y,x);
    }
    

    Dann kann man Array::at bauen, wie man will. Indexgrenzenprüfung, Sparse Matrix als Hashtable, x-Dimension compilezeitkonstand und/oder als Zweierpotenz, all das kann man so machen, ohne die op[][]-Sache damit belasten zu müssen.
    Also ungefähr wie http://www.parashift.com/c++-faq-lite/operator-overloading.html#faq-13.11 , nur haben die IMHO nicht weit genug durchgezogen.



  • Indexgrenzenprüfung würde ich so machen wie es die STL macht, bei [] nicht, bei at() schon. Es bräuchte dann ein unchecked_at() mit einem assert. Die Idee ist nicht schlecht ...
    Meine Version ist halt von Hand maximal optimiert, wenn der Compiler deine Version so stark optimieren könnte, dass sie identisch wären dann ist sie klar die bessere.
    Dass deine flexibler wäre, stimmt allerdings nicht, mein Proxy kann auch template-Parameter (compilezeitkonstande x-Dimension) entgegennehmen und beliebig umgebaut werden, sie ist nur etwas eleganter.

    @Kladdi: wenn du nie in einer Funktion so etwas wie const Matrix& m entgegennimmst, kannst du diese Zeile ignorieren oder löschen (sie hat etwas mit Const-correctness zu tun).



  • Welchen genauen Zweck erfüllt die Proxy-Klasse?



  • Nick Unbekannt schrieb:

    Welchen genauen Zweck erfüllt die Proxy-Klasse?

    a=m[3][4]
    ist
    a=(m[3])[4]
    oder
    Proxy tmp=m[3];
    a=tmp[4];
    Sie erfüllt den Zweck, daß man einen operator[][] überhaupt anbieten kann. Das kann man nämlich gar nicht, weil C++ nur einen operator[] kennt. Mit dem Proxy-Trick gehts dann aber doch.



  • Okay ist klar. Danke für die Erläuterung.



  • Dilkad schrieb:

    Indexgrenzenprüfung würde ich so machen wie es die STL macht, bei [] nicht, bei at() schon. Es bräuchte dann ein unchecked_at() mit einem assert.

    Warum nicht wie die STL machen und at() mit Exception und operator[] mit assert ? Von mir aus kann man auch auf at() verzichten, ich hab das eh noch nie sinnvoll einsetzen können. Hier wäre ein at() höchstens praktisch, weil man gleich zwei Indizes angeben könnte.



  • Nexus schrieb:

    Dilkad schrieb:

    Indexgrenzenprüfung würde ich so machen wie es die STL macht, bei [] nicht, bei at() schon. Es bräuchte dann ein unchecked_at() mit einem assert.

    Warum nicht wie die STL machen und at() mit Exception und operator[] mit assert ? Von mir aus kann man auch auf at() verzichten, ich hab das eh noch nie sinnvoll einsetzen können. Hier wäre ein at() höchstens praktisch, weil man gleich zwei Indizes angeben könnte.

    Ich meine at nicht als Funktion, die jemand anderes als der Proxy benutzen soll. Um den Proxy nur dummen Proxy sein zu lassen.



  • float acess_dilkad_matrix(a::matrix a, int x, int y) { return a[x][y]; }
       0:	55                   	push   %ebp
       1:	89 e5                	mov    %esp,%ebp
       3:	8b 45 08             	mov    0x8(%ebp),%eax
       6:	c1 e0 02             	shl    $0x2,%eax
       9:	0f af 45 14          	imul   0x14(%ebp),%eax
       d:	8b 55 18             	mov    0x18(%ebp),%edx
      10:	c1 e2 02             	shl    $0x2,%edx
      13:	03 55 10             	add    0x10(%ebp),%edx
      16:	d9 04 02             	flds   (%edx,%eax,1)
      19:	5d                   	pop    %ebp
      1a:	c3                   	ret    
    
    float access_parashift_matrix(b::matrix b, int x, int y) { return b(x, y); }
      1b:	55                   	push   %ebp
      1c:	89 e5                	mov    %esp,%ebp
      1e:	8b 55 14             	mov    0x14(%ebp),%edx
      21:	0f af 55 08          	imul   0x8(%ebp),%edx
      25:	03 55 18             	add    0x18(%ebp),%edx
      28:	8b 45 10             	mov    0x10(%ebp),%eax
      2b:	d9 04 90             	flds   (%eax,%edx,4)
      2e:	5d                   	pop    %ebp
      2f:	c3                   	ret
    
    float access_volkard_matrix(c::matrix c, int x, int y) { return c[x][y]; }
      30:	55                   	push   %ebp
      31:	89 e5                	mov    %esp,%ebp
      33:	83 ec 10             	sub    $0x10,%esp
      36:	8b 45 14             	mov    0x14(%ebp),%eax
      39:	ba 00 00 00 00       	mov    $0x0,%edx
      3e:	89 45 f8             	mov    %eax,-0x8(%ebp)
      41:	89 55 fc             	mov    %edx,-0x4(%ebp)
      44:	df 6d f8             	fildll -0x8(%ebp)
      47:	d9 7d f6             	fnstcw -0xa(%ebp)
      4a:	0f b7 45 f6          	movzwl -0xa(%ebp),%eax
      4e:	b4 0c                	mov    $0xc,%ah
      50:	66 89 45 f4          	mov    %ax,-0xc(%ebp)
      54:	d9 6d f4             	fldcw  -0xc(%ebp)
      57:	db 5d f0             	fistpl -0x10(%ebp)
      5a:	d9 6d f6             	fldcw  -0xa(%ebp)
      5d:	8b 55 f0             	mov    -0x10(%ebp),%edx
      60:	0f af 55 08          	imul   0x8(%ebp),%edx
      64:	03 55 18             	add    0x18(%ebp),%edx
      67:	8b 45 10             	mov    0x10(%ebp),%eax
      6a:	d9 04 90             	flds   (%eax,%edx,4)
      6d:	c9                   	leave  
      6e:	c3                   	ret
    

    (erstellt mit g++ -c --optimize a.cc && objdump -S a.o >a.S` ', Code hier verfügbar: http://codepad.org/dJptPyWr)

    Ich werde wohl bei meiner Variante bleiben, obwohl die ()-Variante zwei left-shifts einspart. Ehrlich gesagt hat mich der C++-Compiler hier ein wenig enttäuscht...



  • Dilkad schrieb:

    float acess_dilkad_matrix(a::matrix a, int x, int y) { return a[x][y]; }
       0:	55                   	push   %ebp
       1:	89 e5                	mov    %esp,%ebp
       3:	8b 45 08             	mov    0x8(%ebp),%eax
       6:	c1 e0 02             	shl    $0x2,%eax
       9:	0f af 45 14          	imul   0x14(%ebp),%eax
       d:	8b 55 18             	mov    0x18(%ebp),%edx
      10:	c1 e2 02             	shl    $0x2,%edx
      13:	03 55 10             	add    0x10(%ebp),%edx
      16:	d9 04 02             	flds   (%edx,%eax,1)
      19:	5d                   	pop    %ebp
      1a:	c3                   	ret    
    
    float access_parashift_matrix(b::matrix b, int x, int y) { return b(x, y); }
      1b:	55                   	push   %ebp
      1c:	89 e5                	mov    %esp,%ebp
      1e:	8b 55 14             	mov    0x14(%ebp),%edx
      21:	0f af 55 08          	imul   0x8(%ebp),%edx
      25:	03 55 18             	add    0x18(%ebp),%edx
      28:	8b 45 10             	mov    0x10(%ebp),%eax
      2b:	d9 04 90             	flds   (%eax,%edx,4)
      2e:	5d                   	pop    %ebp
      2f:	c3                   	ret
    
    float access_volkard_matrix(c::matrix c, int x, int y) { return c[x][y]; }
      30:	55                   	push   %ebp
      31:	89 e5                	mov    %esp,%ebp
      33:	83 ec 10             	sub    $0x10,%esp
      36:	8b 45 14             	mov    0x14(%ebp),%eax
      39:	ba 00 00 00 00       	mov    $0x0,%edx
      3e:	89 45 f8             	mov    %eax,-0x8(%ebp)
      41:	89 55 fc             	mov    %edx,-0x4(%ebp)
      44:	df 6d f8             	fildll -0x8(%ebp)
      47:	d9 7d f6             	fnstcw -0xa(%ebp)
      4a:	0f b7 45 f6          	movzwl -0xa(%ebp),%eax
      4e:	b4 0c                	mov    $0xc,%ah
      50:	66 89 45 f4          	mov    %ax,-0xc(%ebp)
      54:	d9 6d f4             	fldcw  -0xc(%ebp)
      57:	db 5d f0             	fistpl -0x10(%ebp)
      5a:	d9 6d f6             	fldcw  -0xa(%ebp)
      5d:	8b 55 f0             	mov    -0x10(%ebp),%edx
      60:	0f af 55 08          	imul   0x8(%ebp),%edx
      64:	03 55 18             	add    0x18(%ebp),%edx
      67:	8b 45 10             	mov    0x10(%ebp),%eax
      6a:	d9 04 90             	flds   (%eax,%edx,4)
      6d:	c9                   	leave  
      6e:	c3                   	ret
    

    (erstellt mit g++ -c --optimize a.cc && objdump -S a.o >a.S` ', Code hier verfügbar: http://codepad.org/dJptPyWr)

    Ich werde wohl bei meiner Variante bleiben, obwohl die ()-Variante zwei left-shifts einspart. Ehrlich gesagt hat mich der C++-Compiler hier ein wenig enttäuscht...

    Ich fürchte, der Test ist nicht fair.
    Erstmal mache ich aux [x][y] das [y][x], wie dichter am nativen float[][] ist.
    Dann mache ich bei volkard den Index zum size_t statt float.
    Und ich ziehe den wichtigen data-Zeiger vor, damit er erstes (und im Zweifelsfall schnellstes) Attribut ist.

    _Z20access_dilkad_matrixRN1a6matrixEii:
    .LFB1034:
    	.cfi_startproc
    	.cfi_personality 0x3,__gxx_personality_v0
    	movq	16(%rdi), %rax
    	movslq	%edx,%rdx
    	movslq	%esi,%rsi
    	salq	$2, %rsi
    	addq	(%rdi), %rsi
    	salq	$2, %rax
    	imulq	%rdx, %rax
    	movss	(%rsi,%rax), %xmm0
    	ret
    
    _Z23access_parashift_matrixRN1b6matrixEii:
    .LFB1035:
    	.cfi_startproc
    	.cfi_personality 0x3,__gxx_personality_v0
    	movslq	%edx,%rdx
    	movq	(%rdi), %rax
    	movslq	%esi,%rsi
    	imulq	16(%rdi), %rdx
    	addq	%rsi, %rdx
    	movss	(%rax,%rdx,4), %xmm0
    	ret
    
    _Z21access_volkard_matrixRN1c6matrixEii:
    .LFB1036:
    	.cfi_startproc
    	.cfi_personality 0x3,__gxx_personality_v0
    	movslq	%edx,%rdx
    	movq	(%rdi), %rax
    	movslq	%esi,%rsi
    	imulq	16(%rdi), %rdx
    	addq	%rsi, %rdx
    	movss	(%rax,%rdx,4), %xmm0
    	ret
    

    Hmm.

    Geschaut mit g++ -march=native -O3 -save-temps main.cpp

    #include <iostream>
    #include <cstdlib>
    using namespace std;
    
    namespace a {
      class matrix {
      public:
        explicit matrix(size_t h, size_t w)
          : data(new float[h*w]),height(h), width(w) {}
        struct proxy {
          explicit proxy(float *data) : data(data){}
          float& operator[](size_t x) { return data[x]; }
          float *data;
        };
        proxy operator[](size_t y) { return proxy(data+y*width); }
      private:
        float *data;
        size_t height, width;
      };
    }
    namespace b {
      class matrix {
      public:
        explicit matrix(size_t h, size_t w)
          : data(new float[h*w]), height(h), width(w) {}
        float& operator()(size_t y, size_t x) { return data[y*width+x]; }
      private:
        float *data;
        size_t height, width;
      };
    }
    namespace c {
      class matrix {
      public:
        explicit matrix(size_t h, size_t w)
        : data(new float[w*h]), height(h), width(w) {}
        struct proxy {
          explicit proxy(matrix *m, size_t y) : m(m), y(y) {}
          float& operator[](size_t x) { return m->unchecked_at(y,x); }
          matrix *m;
          size_t y;
        };
        proxy operator[](size_t y) { return proxy(this, y); }
        float& unchecked_at(int y, int x) { return data[y*width+x]; }
      private:
        float *data;
        size_t height, width;
      };
    }
    
    float access_dilkad_matrix(a::matrix& a, int x, int y)
    {
      return a[y][x];
    }
    float access_parashift_matrix(b::matrix& b, int x, int y)
    {
      return b(y, x);
    }
    float access_volkard_matrix(c::matrix& c, int x, int y)
    {
      return c[y][x];
    }
    
    int main(){
        a::matrix m(10,10);
        for(int y=0;y!=10;++y)
            for(int x=0;x!=10;++x)
                m[y][x]=rand();
        int y=rand()%10;
        int x=rand()%10;
        int t=access_dilkad_matrix(m,x,y);
        cout<<t<<'\n';
    }
    

    Ich werde wohl bei meiner Variante bleiben, weil mir scheint, der dumme Proxy kostet nichts, und innerhalb von unchecked_at sieht der Compiler im Zweifelsfall mehr Optimierungen, als wenn ich die Funktionalität auf mehrere Funktionen spalte. Und nicht zuletzt, es gefällt mir einfach, wenn der Proxy nur Proxy ist. So würde ich es auch machen, wenn das Compilat nur geringfügig langsamer wäre. Es ist typisch für mich, daß ich hübsche Sachen auch stehen lasse, wenn sie prinzipiell gut optimierbar sind, aber ich für den vollen Spaß noch eine oder zwei neue Compilerversionen abwarten muß.
    Der Compiler hat mich wiedermal nicht enttäuscht. 🙂



  • volkard schrieb:

    Erstmal mache ich aux [x][y] das [y][x], wie dichter am nativen float[][] ist.

    Geschwindigkeitsmässig dürfte sich da nichts ändern, aber du hast Recht; hier verhält sich meine Matrix nicht wie ein float[][] , das dürfte nicht vor kommen.

    volkard schrieb:

    Dann mache ich bei volkard den Index zum size_t statt float.

    Oh, ein echter Bug. Das wird vermutlich der Grund für "dein" schlechtes Abschneiden in meinem Test sein.

    volkard schrieb:

    Und ich ziehe den wichtigen data-Zeiger vor, damit er erstes (und im Zweifelsfall schnellstes) Attribut ist.

    Ganz ehrlich: das war ein (wieder mal zu früh umgesetzer) Trick um die c::matrix auszubremsen. Hier habe ich jedoch nicht wirklich tief nachgedacht, den das trifft ja alle gleich stark.

    volkard schrieb:

    Hmm.

    Bei meinem Compiler (g++ (Debian 4.4.5-8) 4.4.5) tritt der Vergleich weniger krass zu Tage - volkard unterscheidet sich an zwei Assemblerbefehlen von parashift, dilkard hat einen Befehl mehr - aber dass meine handoptimierte Matrix nicht nur gleichauf, nein sogar schlechter abschneidet als deine, das gibt mir zu denken.

    volkard schrieb:

    Und nicht zuletzt, es gefällt mir einfach, wenn der Proxy nur Proxy ist. So würde ich es auch machen, wenn das Compilat nur geringfügig langsamer wäre. Es ist typisch für mich, daß ich hübsche Sachen auch stehen lasse, wenn sie prinzipiell gut optimierbar sind, aber ich für den vollen Spaß noch eine oder zwei neue Compilerversionen abwarten muß.

    Naja, ob der Proxy jetzt hübsch oder unschön ist, ist mir relativ egal, da der Benutzer es nicht mitbekommt/mitbekommen sollte. In klasseninterner Implementierung steht die Eleganz immer unter der Performanz, solange das Interface nicht darunter leidet (und das tut es bei der parashift-Methode).
    Wenn ich aber weiss, dass eine volkard-Matrix besser ist, als meine jetzige, werde ich das in Zukunft berücksichtigen.
    Stört mich nur noch eins: boost::multi_array<float, 2> ist 11 mal langsamer als dilkad.

    Jetzt, da meine eigenen Code-Fehler ausgemerzt sind, kann ich dir nur zustimmen:

    volkard schrieb:

    Der Compiler hat mich wiedermal nicht enttäuscht.

    🙂


Anmelden zum Antworten