Newsgroup: it.comp.lang.c++
Titolo: array di oggetti...

Salve!
Ecco, il mio problema riguarda proprio gli array di oggetti, ma non oggetti
qualunque, bensi' oggetti che hanno un costruttore con argomenti. Prendiamo
ad esmpio il caso di un array di matrici (che, guarda caso, e' quello per
cui ho avuto i problemi, ma e' una questione generale).
Per automatizzare l'utilizzo di matrici allocate dinamicamente ho fatto una
classe matrix, il cui costruttore prende per parametri il numero di righe e
di colonne della matrice.
Potrei voler avere un array di matrici di differente dimensione? Penso di
si': se per esempio avessi implementato la matrice con una struct, e avessi
lasciato allocazione e deallocazione a dei metodi esterni, non penso sarebbe
stato difficile.
Invece cosi' come faccio?
Scrivendo:
matrix <int>* m1=new matrix<int>[50];
non gli va (ovviamente) bene, ma non posso nemmeno scrivere:
matrix <int>* m1=new matrix<int>[50](nRighe, nColonne);
perche' sarebbero sempre le stesse.
Nello Stroustrup c'e' scritto che non si puo' fare facilmente, e fa un
esempio che non mi sembra molto utile: in pratica nell'esempio fa un
costruttore senza parametri che per prendere i parametri utilizza uno
istream cin>>....
Non e' che qualcuno sa un modo per risolvere la questione?
Grazie,
Zeppe

 matrix <int>* m1=new matrix<int>[50];
Postilla che non c'entra nulla:
Sto anche io implementando una classe matrix template.
E' possibile dare un'occhiata al tuo codice che magari ci trovo
l'ispirazione?
Il mio lo trovi su questo NG nel thread "Cerco Libreria" del
31-3-2002
ciao
--
Gerolamo

 31-3-2002
purtroppo non vedo il mex... boh!
Comunque, certamente metto il mio, se t'interessa, poiche', dato che ho
iniziato ieri, non ho ancora overloadato nessun operatore (a parte l'=).
Beh, ecco qui:
----------------------------------------------------------------------------
-------
template <class m_item>
class matrix {
    private:
        int nRows;
        int nColumns;
    public:
        m_item** v;
        matrix (const int, const int);
        matrix (const matrix <m_item> &);
        ~matrix ();
        const matrix & operator= (const matrix <m_item> &);
        int Rows () const { return this->nRows; }
        int Columns () const { return this->nColumns; }
        m_item* RMax() const;
        m_item* RMin() const;
};
template <class m_item>
matrix<m_item>::matrix(const int nRows, const int nColumns): nRows(nRows),
nColumns(nColumns) {
    this->v=new (m_item*)[this->nRows];
    if (!this->v) throw -1;
        for (int i=0; i<this->nRows; i++) {
            this->v[i]=new (m_item)[this->nColumns];
            if (!this->v[i]) throw -1;
        }
}
template <class m_item>
matrix<m_item>::matrix(const matrix <m_item> & m1): nRows(m1.nRows),
nColumns(m1.nColumns) {
    this->v=new (m_item*)[this->nRows];
    if (!this->v) throw -1;
        for (int i=0; i<this->nRows; i++) {
            this->v[i]=new (m_item)[this->nColumns];
            if (!this->v[i]) throw -1;
        }
    for (int i=0; i<this->nRows; i++)
        for (int j=0; j<this->nColumns; j++)
            this->v[i][j]=m1.v[i][j];
}
template <class m_item>
matrix<m_item>::~matrix(){
    for (int i=0; i<this->nRows; i++)
        delete [] this->v[i];
    delete [] this->v;
}
template <class m_item>
const matrix <m_item> & matrix<m_item>::operator= (const matrix <m_item> &
m1) {
    for (int i=0; i<this->nRows; i++)
        if (this->v[i])delete [] this->v[i];
    if (this->v) delete [] this->v;
    this->nRows=m1.nRows;
    this->nColumns=m1.nColumns;
    this->v=new (m_item*)[this->nRows];
    if (!this->v) throw -1;
        for (int i=0; i<this->nRows; i++) {
            this->v[i]=new (m_item)[this->nColumns];
            if (!this->v[i]) throw -1;
        }
    for (int i=0; i<this->nRows; i++)
        for (int j=0; j<this->nColumns; j++)
            this->v[i][j]=m1.v[i][j];
}
template <class m_item>
void display (const matrix <m_item> & m){
    std::cout<<"matrice "<<m.Rows()<<'x'<<m.Columns()<<endl<<endl;
    for (int i=0; i<m.Rows(); i++){
        for (int j=0; j<m.Columns(); j++)
            printf("|_%2d", m.v[i][j]);
       std::cout<<"_|\n";
    }
}
__----------------------------------------------
ciao,
Zeppe

 Beh, ecco qui:
Interessante... abbiamo un approccio piuttosto diverso alla questione.
Al volo non ho il tempo di guardare il tuo codice, ma appena
posso lo faccio.
intanto ti saluto
--
Gerolamo

 argomenti. 
La soluzione semplice e`: usa std::vector. Ad esempio:
struct Prova {
        Prova(int) { } // nessun default ctor
};
// 10 elementi inizializzati con il copy ctor da Prova(1)
std::vector<Prova> v(10, Prova(1)); 
// 10 elem. inizializzati con il ctor Prova(int) direttamente
std::vector<Prova> v(10, 1);
Volendo approfondire, quello che citi tu e` un problema abbastanza noto: 
in pratica quando si definisce un array di oggetti essi devono essere 
per forza inizializzati con il costruttore di default e se non e` 
presente abbiamo un errore di compilazione. Ad esempio:
Prova array[3]; // errore
Il problema puo` essere parzialmente risolto con le liste di 
inizializzazione degli array:
Prova array[3] = { 3, 6, 9 }; // corretto
Purtroppo questa soluzione non e` facilmente scalabile quando gli 
oggetti sono molti e soprattutto non e` sempre possibile utilizzarla 
quando entrano in gioco i template; ad esempio:
template<int N>
class FixedArrayOfProva
{
        Prova array[N] = { /* cosa mettere qua dentro? */ };
public:
        ...
};
In ogni caso non tutte le speranze sono perdute: possiamo risolvere 
implementando manualmente quello che succede quando il compilatore 
alloca un array. In pratica dobbiamo fare due cose:
1) allocare la memoria necessaria
2) richiamare il costruttore per ogni elemento
Come fare a risolvere il punto 1?
In questo caso lo standard ci viene in aiuto:
a) sappiamo che sizeof(char) == 1
b) sappiamo che sizeof( T[3] ) == 3 * sizeof(T), qualsiasi sia il tipo T
A questo punto possiamo allocare la memoria per N oggetti di tipo Prova 
in questo modo:
char raw_memory[ N* sizeof(Prova) ];
Ora non ci rimane che inizializzare i vari elementi dell'array; per fare 
questo abbiamo bisogno di un modo per dire al compilatore: "costruisci 
un oggetto prova all'indirizzo di memoria XXX": esiste una versione 
dell'operatore new, detta placement new, che esegue proprio questo 
compito:
// costruisce gli oggetti dell'array con i valori 0,1,4,9,16,25...
  Prova* array = (Prova*)raw_memory; // dubbio, vedere sotto
  for(int i=0; i<N; i++) 
    {
      void* buf = static_cast<void*>(&array[i]);
      new(buf) Prova(i*i); // placement new
    }
A questo punto e` possibile utilizzare la variabile array esattamente (o 
quasi - in alcuni casi array e puntatori si comportano in maniera 
diversa) come se fosse un array allocato automaticamente dal compilatore.
Il codice che ho scritto dovrebbe essere assolutamente portabile, a 
parte la riga che ho marcato con "dubbio" sulla quale non sono 
eccessivamente sicuro: e` una pratica sicura?
Provando ad utilizzare i cast c++-style ho notato che il semplice 
static_cast non funziona:
        Prova* array = static_cast<Prova*>(raw_memory); // errore
per risolvere e` necessario un reinterpret_cast
        Prova* array = reinterpret_cast<Prova*>(raw_memory); // OK
purtroppo il reinterpret_cast e` per definizione implementation defined, 
quindi non e` molto utile se il nostro obiettivo e` la portabilita`.
Pero` dobbiamo anche considerare che per passare da un T* a void* e 
viceversa basta uno static_cast, quindi il seguente codice e` corretto:
        void* tmp = static_cast<void*>(raw_memory);
        Prova* array = static_cast<Prova*>(tmp);
E` anche portabile oppure ci possono essere dei problemi?
ciao Anto
-- 
Antonio Cuni, 19 anni, studente
Brainbench C++ Master: 4.73, higher than 99% of all previous test takers.
C Language: 3.79, higher than 92% of all previous test takers.
Guida C/C++ di Programmazione.it: 
http://cpp.programmazione.it

 posso lo faccio.
Beh, posso dire che la tua mi sembra innanzitutto molto piu' completa: nella
mia non ci sono nemmeno ancora gli operatori, la gestione degli errori e'
inesistente e tutto...
se andando avanti avra' un aspetto migliore te la potrei mandare...
Ciao,
Zeppe

     }
ovviamente mi sono dimenticato si specificare una cosa importantissima: 
dato che abbiamo costrito gli oggetti "a mano" dobbiamo anche 
distruggerli allo stesso modo:
// richiama i distruttori in ordine inverso a come sono stati costruiti
for(int i=N-1; i>=0; i--)
        array[i].~Prova();


 Volendo approfondire, quello che citi tu e` un problema abbastanza noto:
[CUT]
ehm, effettivamente la soluzione che hai dato tu non e' proprio banale, per
uno alle prime armi come me... devo rifletterci un po'. L'idea che poi mi e'
venuta, e' quella di fare un costruttore:
//--------------------
template <class m_item>
matrix<m_item>::matrix(const int* nRows, const int* nColumns,int & n):
nRows(nRows[n]), nColumns(nColumns[n]) {
    this->v=new (m_item*)[this->nRows];
    if (!this->v) throw -1;
        for (int i=0; i<this->nRows; i++) {
            this->v[i]=new (m_item)[this->nColumns];
            if (!this->v[i]) throw -1;
        }
    n++;
}
//-------------------
Preaticmante gli si passa due vettori nei cui elementi n-esimi ci sono
numero di righe e di colonne della matrice n-esima. Il problema e' che come
argomento bisogna per forza passargli anche uno static int inizializzato a
0. Ci sarebbe qualche modo per fare cio' in maniera invisibile? Potrei
cercare nella ridefinizione di new, ma non l'ho mai fatta e non so se sia
d'aiuto...
Ciao,
Zeppe