Questo sito utilizza cookies, anche di terze parti, per mostrare pubblicità e servizi in linea con il tuo account. Leggi l'informativa sui cookies.
Username: Password: oppure
C/C++ - Il mio primo template. Come vado?
Forum - C/C++ - Il mio primo template. Come vado?

Pagine: [ 1 2 3 ] Precedente | Prossimo
Avatar
AldoBaldo (Member)
Expert


Messaggi: 388
Iscritto: 08/01/2015

Segnala al moderatore
Postato alle 18:45
Venerdì, 07/09/2018
"Ispirato" dalla discussione sulle "matrici" propostaci da comtel, ho voluto provare a mettere insieme un template che si occupi (in C++, ovviamente) di quel tipo di costrutto, ovvero di "finte" matrici implementate in memoria come vettori. Siccome è la prima volta che metto le mani sui template, mi farebbe piacere sapere se ho applicato correttamente quel che avrei dovuto applicare correttamente. Intendo, il codice sembra funzionare ma... mi sfugge qualche particolare importante che dovrei conoscere e del quale dovrei tenere conto?

Codice sorgente - presumibilmente C++

  1. #ifndef TEMPLATE_MATRICE_H
  2. #define TEMPLATE_MATRICE_H
  3.  
  4. #include <stdlib.h>
  5.  
  6. template<class TIPO>
  7. class MATRICE {
  8.     public:
  9.         MATRICE();
  10.         MATRICE( size_t colonne, size_t righe );
  11.         virtual ~MATRICE();
  12.         MATRICE(const MATRICE& other);
  13.  
  14.         // modifica
  15.         bool Dimensiona(
  16.             size_t colonne, size_t righe, bool preserva_dati = true );
  17.  
  18.         // lettura
  19.         size_t QColonne( void ) const { return qc; }
  20.         size_t QRighe( void )   const { return qr; }
  21.         size_t QCelle( void )   const { return qc*qr; }
  22.  
  23.         MATRICE& operator=(const MATRICE& other);
  24.         TIPO& operator()( size_t x, size_t y );
  25.  
  26.         static const char *kStrErrNoDim;
  27.         static const char *kStrErrNoCoo;
  28.  
  29.     protected:
  30.  
  31.     private:
  32.         TIPO *m;    // l'array che contiene la matrice
  33.         size_t qr;  // quantita' di righe
  34.         size_t qc;  // quantita' di colonne
  35. };
  36.  
  37. template<class TIPO>const char *MATRICE<TIPO>::kStrErrNoDim =
  38.     "template MATRICE: dimensionamento fallito";
  39.  
  40. template<class TIPO>const char *MATRICE<TIPO>::kStrErrNoCoo =
  41.     "template MATRICE: coordinate esterne alla matrice";
  42.  
  43. template<class TIPO>
  44. MATRICE<TIPO>::MATRICE() {
  45.     m = NULL;
  46.     qc = qr = 0;
  47. }
  48.  
  49. template<class TIPO>
  50. MATRICE<TIPO>::MATRICE( size_t colonne, size_t righe ) {
  51.     m = NULL;
  52.     qc = qr = 0;
  53.  
  54.     if( !Dimensiona(colonne,righe) ) throw kStrErrNoDim;
  55. }
  56.  
  57. template<class TIPO>
  58. MATRICE<TIPO>::~MATRICE() {
  59.     if( m ) delete[] m;
  60. }
  61.  
  62. template<class TIPO>
  63. bool MATRICE<TIPO>::Dimensiona(
  64.     size_t colonne, size_t righe, bool preserva_dati ) {
  65.  
  66.     try {
  67.         TIPO *mAux = new TIPO[colonne*righe];
  68.  
  69.         if( preserva_dati ) {
  70.             for( size_t max_rig=righe<qr?righe:qr, r=0; r<max_rig; ++r )
  71.                 for( size_t max_col=colonne<qc?colonne:qc, c=0; c<max_col; ++c )
  72.                     mAux[r*max_col+c] = m[r*qc+c];
  73.         }
  74.  
  75.         if( m ) delete[] m;
  76.  
  77.         m  = mAux;
  78.         qc = colonne;
  79.         qr = righe;
  80.  
  81.         return true;
  82.     } catch( ... ) {
  83.         return false;
  84.     }
  85. }
  86.  
  87. template<class TIPO>
  88. MATRICE<TIPO>::MATRICE(const MATRICE& other) {
  89.     m = NULL;
  90.     qc = qr = 0;
  91.     *this = other;
  92. }
  93.  
  94. template<class TIPO>
  95. MATRICE<TIPO>& MATRICE<TIPO>::operator=(const MATRICE<TIPO>& rhs) {
  96.     if (this == &rhs) return *this; // handle self assignment
  97.  
  98.     if( !Dimensiona(rhs.qc,rhs.qr,false) ) throw kStrErrNoDim;
  99.  
  100.     for( size_t r=0; r<qr; ++r )
  101.         for( size_t c=0; c<qc; ++c )
  102.             m[r*qc+c] = rhs.m[r*qc+c];
  103.  
  104.     return *this;
  105. }
  106.  
  107. template<class TIPO>
  108. TIPO& MATRICE<TIPO>::operator()( size_t x, size_t y ) {
  109.     if( x>=qc||y>=qr ) throw kStrErrNoCoo;
  110.     return m[y*qc+x];
  111. }
  112.  
  113. #endif // TEMPLATE_MATRICE_H



EDIT: siccome queste cose le conservo per poi riutilizzarle magari anche dopo un bel po' di tempo, mi son preparato un file di promemoria che ho messo in http://tinyurl.com/y93dgo73

Ultima modifica effettuata da AldoBaldo il 07/09/2018 alle 21:44


Ma cosa vuoi che ne sappia? Io ci gioco, col codice, mica ci lavoro!
PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1542
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 23:35
Venerdì, 07/09/2018
Nell'operatore di copia ti sei dimenticato di liberare la memoria attualmente utilizzata prima di fare la copia profonda....

PM Quote
Avatar
AldoBaldo (Member)
Expert


Messaggi: 388
Iscritto: 08/01/2015

Segnala al moderatore
Postato alle 0:24
Sabato, 08/09/2018
Parli del costruttore di copie (MATRICE(const MATRICE& other);, riga 12) o dell'operatore "=" (riga 23)? In entrambi i casi dovrebbe farsene carico il "if( m ) delete[] m;" che c'è alla riga 75, nell'ambito del metodo Dimensiona(). Almeno, questo secondo le mie intenzioni. Mi sfugge qualcosa? Non riesco a vedere cosa.


Ma cosa vuoi che ne sappia? Io ci gioco, col codice, mica ci lavoro!
PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1542
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 1:23
Sabato, 08/09/2018
Hai ragione, non lo avevo visto.

Allora piccola nota: sposta quel pezzo di codice in una funzione apposita, così organizzi meglio il codice e attinenti la leggibilità.

PM Quote
Avatar
AldoBaldo (Member)
Expert


Messaggi: 388
Iscritto: 08/01/2015

Segnala al moderatore
Postato alle 7:52
Sabato, 08/09/2018
Tipo un metodo privato chiamato, che so, "DistruggiOggettiDinamici()"? Qualche nome migliore ti viene in mente?

Riguardando il codice...

1) Pensavo di rendere inline l'overload dell'operatore "()", quindi ho fatto un po' di test e... sorpresa!... le prestazioni non cambiano d'una virgola neppure nel caso di "matrici" molto grandi e cicli di ripetizioni d'accesso enormi. Non è strano? Da quel che ho sempre letto, i metodi inline dovrebbero dar luogo a codice più "ponderoso" in termini di dimensioni ma più celere in termini di prestazioni. Mah...

Ancora...

2) Ho cambiato il codice di quell'operatore in questo modo:

Codice sorgente - presumibilmente C++

  1. // vecchia versione
  2. if( x>=qc||y>=qr ) throw kStrErrNoCoo;
  3.     return m[y*qc+x];
  4.  
  5. // nuova versione
  6. if( x<qc&&y<qr ) return m[y*qc+x];
  7.     throw kStrErrNoCoo;



Anche qui, stando a quel che ho letto da più di una fonte mi aspettavo un minimo quid di miglioramento nelle prestazioni, ma dai test non è cambiato assolutamente nulla.

Ultima cosa...

3) Mi sto chiedendo se non sia il caso di "isolare" la copia dei vecchi dati in un apposito try per intercettare le eventuali eccezioni lanciate dagli oggetti copiati e "convertirle" in eccezioni legate alla "matrice" anziché alla classe degli oggetti copiati stessi. Tu che faresti?

(che bella cosa i forum dove ti rispondono senza darti della bestia ogni tre per due! :heehee:)


Ma cosa vuoi che ne sappia? Io ci gioco, col codice, mica ci lavoro!
PM Quote
Avatar
lumo (Member)
Expert


Messaggi: 441
Iscritto: 18/04/2010

Segnala al moderatore
Postato alle 15:48
Sabato, 08/09/2018
Trovo personalmente poco utile poter ridimensionare la matrice mantenendo i dati interni, comunque se vuoi liberarti di quei try/catch che al tuo stile molto C-oso secondo me non si adattano potresti provare ad usare nothrow (googla, praticamente permette di ritornare nullptr invece di avere un errore).

Invece di farti gli errori tuoi potresti anche pensare ad usare http://www.cplusplus.com/reference/stdexcept/out_of_range/

Riguardo a 1), il costo di una chiamata a funzione non è così grande, comunque può essere che il compilatore abbia ottimizzato il tuo codice rendendole inline lo stesso (l'indicazione inline non è obbligatoria per un compilatore) oppure che il tuo codice di test non sia corretto.

Perché ti aspetti un miglioramento delle prestazioni da 2?

PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1542
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 3:06
Domenica, 09/09/2018
AldoBaldo, un consiglio spassionato: scrivi codice con buon gusto e stile, lascia fuori il superfluo, mantieni i dati relazionati il più vicino possibile.... Il resto non ha senso. Non usare inline se non è un requisito estremamente necessario per la funzionalità: i compilatori hanno dietro decine e decine di persone pronte a fare tutto il necessario pergenerare codice il più veloce possibile ed onestamente potresti scoprire che i tuoi sforzi sono oltre che inutili, potrebbero essere controproducenti.

PM Quote
Avatar
AldoBaldo (Member)
Expert


Messaggi: 388
Iscritto: 08/01/2015

Segnala al moderatore
Postato alle 9:43
Domenica, 09/09/2018
Apposta ho previsto la possibilità di NON copiare i dati in fase di ridimensionamento: basta aggiungere un terzo parametro (false) a Dimensiona() e il gioco è fatto. Ora che mi fai rileggere il codice, mi accorgo che alla riga 54 (nella funzione creatrice parametrizzata con il numero delle colonne e delle righe) ci sta bene l'aggiunta di quel terzo parametro false alla chiamata "interna" a Dimensiona(). E' ben vero che i cicli verrebbero saltati comunque, però si sostituisce una certa quantità di operazioni col semplice controllo per verificare se preserva_dati è true o false. Senza tornarci non me ne sarei accorto.

Da qui in poi, DEVO premettere che so di avere lacune consistenti con il C++, quindi le mie affermazioni sono tutte a livello di speculazione di chi sta cercando di capire delle cose che si addentrano su un terreno semi-ignoto.

Ho controllato out_of_range. In effetti aggiungerebbe il vantaggio di fare riferimento a un'eccezione standard, però obbligherebbe a includere stdexcept. Il che pare comporti la "iniezione" di una valangata di roba "implicita" nell'eseguibile (ho controllato: un programmino di una manciata di righe, compilato, balza da 113 byte a 720 byte semplicemente aggiungendo un catch che fa riferimento a std::out_of_range!).

Codice sorgente - presumibilmente VB.NET

  1. #include <stdio.h>
  2. #include "template_matrice.h"
  3.  
  4. int main() {
  5.     try {
  6.         MATRICE<int> m(10,10);
  7.         m(9,11) = 31; // secondo parametro esterno alla matrice
  8.     } catch( const char *e ) {
  9.         puts( e );
  10.     } catch( const std::out_of_range& oor ) {
  11.         puts( oor.what() );
  12.     }
  13.  
  14.     return 0;
  15. }
  16.  
  17. // se in template_matrice.h c'e' throw std::out_of_range(kStrErrNoCoo):
  18. // "Output file is bin\Release\prova out_of_range.exe with size 720.50 KB"
  19.  
  20. // se in template_matrice.h c'e' throw kStrErrNoCoo e basta:
  21. // "Output file is bin\Release\prova out_of_range.exe with size 113.50 KB"



Di out_of_range non avevo mai sentito parlare, mentre nothrow già mi era noto. Ti ringrazio per la segnalazione, ma se non ci sono differenze funzionali tra usarlo e non usarlo, in un contesto come questo credo di potermela cavare anche con le "complicazioni" del try/catch. In altri contesti, in effetti, l'ho trovato molto comodo e l'ho usato senza remore perché mi permetteva di aggirare alcune delle mie lacune (dubbi, più che altro; così, per andare sul sicuro...). Consigli il nothrow? Ci metto un attimo a cambiare la formulazione, e sai che di te mi fido.

Sull'ultimo punto... Il codice di test era, banalmente, una serie "ponderosa" di cicli che accedevano ai dati in matrice in lettura e in scrittura, posta tra due clock() per rilevare il tempo in millisecondi. Molto grezzo e poco affidabile, lo so, anche perché quasi sicuramente deve fare i conti con la coesistenza con quel che succede in chissà quanti processi attivi sul computer che "inquinano" il risultato in modi per me del tutto imprevedibili. Diciamo che è un metodo MOLTO spannometrico.

Dal punto 2) mi sarei aspettato delle differenze perché sul testo sul quale studiai anni fa riportava che è preferibile ("più efficiente") mettere il caso che si presenta più spesso nell'if piuttosto che nell'else. Secondo la spiegazione che dava sul testo, l'if viene valutato sempre e comunque, mentre l'else viene valutato solo se l'if non è valido. Noto ora che non ho inserito alcun else, e che si arriva al throw semplicemente se "fallisce" l'if, senza alcun ulteriore controllo (quell'if viene valutato comunque). MAGARI sarebbe stato diverso se avessi scritto if( x<qc&&y<qr ) return m[y*qc+x]; ELSE throw kStrErrNoCoo. Ma else non c'è. Vale il ragionamento o sto travisando/speculando sul sesso degli angeli?


Ma cosa vuoi che ne sappia? Io ci gioco, col codice, mica ci lavoro!
PM Quote
Avatar
AldoBaldo (Member)
Expert


Messaggi: 388
Iscritto: 08/01/2015

Segnala al moderatore
Postato alle 9:54
Domenica, 09/09/2018
TheDarkJuster, il tuo consiglio spassionato lo accolgo senza remore perché direi che esprime buon senso. Devo però chiederti di specificare alcune cose che non ho colto appieno:

1) nel caso specifico, cosa intendi concretamente con "lascia fuori il superfluo"? mi dai delle indicazioni più mirate?

2) quando dici "mantieni i dati relazionati il più vicino possibile" cosa intendi? ho una mezza idea di quel che potrebbe significare, ma non sono certo di aver davvero capito quel che mi vuoi dire (ipotizzo che ti riferisca a cose tipo quel delete[] messo in un "altrove" non immediatamente visibile/identificabile -- magari basterebbe inserire una riga di commento che fa presente la sua esistenza e dove?).


Ma cosa vuoi che ne sappia? Io ci gioco, col codice, mica ci lavoro!
PM Quote
Pagine: [ 1 2 3 ] Precedente | Prossimo