Newsgroup: it.comp.lang.c++
Titolo: Classi dalla genealogia complessa

Ho iniziato a scrivere quel MUD di cui ho parlato in un precedente messaggio
in crosspost anche qui, e vorrei un consiglio su come impostare i rapporti
tra alcune classi che devono far parte del programma.
In particolare, ho creato una classe BufferedSocket che funziona così:
BS << "Questo e' il testo da inviare al socket\n" << "E questo e' un esempio
di intero: " << var;
// altro invio di testo, il tutto viene immagazzinato in un buffer, e poi
BS.sendall(); // invia tutto il contenuto del buffer al socket
Crearla non è stato difficile, in prticolare è bastato (sotto Windows)
derivarla per ereditarietà multipla da CAsyncSocket e ostrstream:
class bufferedSocket: private CAsyncSocket, public ostrstream
{
    // ...
public:
    void sendall(void);
}
La funzione sendall() non fa altro che inviare al socket gestito dalla
classe (creato con il costruttore di CAsyncSocket, una classe MFC) il
contenuto del buffer interno gestito da ostrstream.
Ora, all'interno del gioco ho una classe base per i personaggi, che
ammettiamo di aver chiamato BasicChar; si tratta di un ADT, in quanto viene
utilizzato solo per derivarne le classi PC (per i personaggi giocanti) e NPC
(per quelli non giocanti); la differenza tra le due sottoclassi è che alla
prima è associata una connessione e vengono gestiti i comandi del giocatore,
mentre la seconda non ha nulla di tutto ciò.
Io vorrei che la classe per i personaggi giocanti presentasse un'interfaccia
simile agli ostream, quindi con operatori di inserimento, che però non deve
fare nulla nel caso che il personaggio non sia giocante.
Esempio:
BasicChar *p1, *p2;
p1 = new PC(...); // dati di costruzione
p2 = new NPC(...); //idem
*p1 << "Ciao\n"; // non fa nulla
*p2 << "Ciao\n"; // invia l'output al personaggio
Questo implica chiaramente la definizione virtuale dell'operatore "<<" nella
classe BasicChar, e la ridefinizione nelle sottoclassi.
Quello che voglio sapere è cosa accadrebbe se utilizzassi l'ereditarietà e
derivassi la nuova classe da BasicChar e da BufferedSocket, ad esempio così:
class PC: public BasicChar, public BufferedSocket
{
    // ...
}
La classe PC erediterebbe i metodi di entrambe le genitrici, e quindi una
chiamata come
PC a;
a << "Ciao\n";
funzionerebbe. Però cosa succederebbe al polimorfismo ?
Due esempi in particolare:
BasicChar *p1, *p2;
p1 = new PC(...);
p2 = new NPC(...);
*p1 << "Ciao\n"; // il polimorfismo funziona anche se l'ereditarietà è
multipla ?
*p2 << "Ciao\n"; // e qui che succede ?
Una soluzione al secondo problema sarebbe definire virtualmente l'operatore
"<<" per la classe BasicChar, ma poi al momento di derivarla, nel caso di PC
che cosa accadrebbe ? I metodi della BufferedSocket sovrascriverebbero
quelli della BasicChar ? Oppure... boh, non riesco nemmeno a immaginarlo ?
Il polimorfismo mi serve assolutamente, perché ho una serie di procedure che
possono avere a che fare sia con personaggi giocanti che non giocanti, e
inserire controlli tipo "invia questo testo solo se stai parlando con un
giocatore e non con un pupazzo" mi sembrerebbe un ottimo motivo per tornare
al C puro & semplice...
Un'altra soluzione sarebbe di evitare l'ereditarietà multipla e dire
semplicemente che la PC contiene un puntatore ad un BufferedSocket, mentre
la NPC no. Questo semplifica molte cose, eccone un esempio:
class BasicChar
{
    // ...
    virtual BasicChar & operator << (char *) = 0;
};
class PC: public BasicChar
{
    // ...
    BufferedSocket *socket;
    virtual BasicChar & operator << (char *text)
    {
        *socket << text;
        return *this;
    }
};
class NPC: public BasicChar
{
    // ...
    virtual BasicChar & operator << (char *text)
    {
        return *this;
    }
};
Il problema quì è semplicemente che occorre ridefinire l'operatore "<<" per
*TUTTI* i tipi di dato supportati da BufferedSocket (e quindi da
ostrstream), e calcolando che questo va fatto tre volte (in BasicChar
virtualmente e nelle due figlie realmente) la cosa diventa un po'
scocciante...
Credo che userò questo sistema, perché l'ereditarietà multipla qui
complicherebbe solo le cose, ma mi piacerebbe sapere se esistono altre
soluzioni... anche se si potesse dire qualcosa come "se vedi l'operatore <<,
per *QUALUNQUE TIPO DI DATO*, passalo al socket interno" già mi basterebbe
:-)
Grazie per l'interessamento...
Massimo

 per *QUALUNQUE TIPO DI DATO*, passalo al socket interno" già mi basterebbe
ecco, questa proprio non riesco a capirla... stai parlando x PC, x NPC o x
entrambi? se tutti i dati devono essere passati al socket interno, a cosa
serve BasicChar? dichiara PC e NPC come derivate di BufferedSocket e stop...
illuminami tu a questo punto!

 illuminami tu a questo punto!
E' semplice.
L'operatore, *PER OGNI TIPO DI DATO*, dev'essere definito in tutte e tre le
classi:
class BasicChar
{
    BasicChar & operator << (char *) = 0;
}
class PC: public BasicChar
{
    BasicChar & operator << (char *text)
    {
        *socket << text;
        return (*this);
    }
}
class NPC: public BasicChar
{
    BasicChar & operator << (char *text)
    {
        return (*this);
    }
}
Sono poche righe di codice, ma moltiplicandole per ogni tipo base di dati, è
una rottura di scatole notevole...
Massimo

 una rottura di scatole notevole...
uhm, penso di avere capito... che forse devi dare una riguardata a come hai
organizzato le cose! Se pensi di avere un problema prova a fare cosě:
dimentica quello che hai fatto fino a quel momento e prova un approccio
completamente diverso...
In ogni caso, potresti fare cosě:
class BufferedSocket
{
    virtual BufferedSocket &operator <<( const char *)
    {    //    definizione}
    //    e cosě via x tutti gli altri tipi di cui hai bisogno
};
class BasicChar: public BufferedSocket
{
    //    quello di cui hai bisogno
};
class PC:    public BasicChar
{
    //    non hai bisogno di ridefinire i metodi di prima
};
class NPC:    public BasicChar
{
    NPC &operator <<( const char *)
    {    return *this;}
    //    qui perň ti tocca ridefinirli tutti
};
in questo modo devi ridefinirli una volta di meno. In ogni caso, se vuoi
proprio fare nella maniera in cui hai illustrato, almeno due volte ciascuno
gli operator << devi ridefinirli.

 gli operator << devi ridefinirli.
Vado comunque a finire a tre... perché la classe BasicChar mi serve proprio
per derivarne PC (che ha un socket) e NPC (che non lo ha). Infilare
BufferedSocket in BasicChar annullerebbe l'unica differenza tra le due
sottoclassi, e a quel punto tanto varrebbe averne solo una...
Vabbé, scriverò questa cinquantina di righe... appena finiscono 'sti
benedetti esami (giusto oggi ho fatto il botto con logica matematica
:-((( ).
Massimo

beh, usare una funzione template potrebbe essere una soluzione per sbrigarti
il compito (scusa, mi sono venute in mente solo adesso). Per esempio:
template< typename obj> PC &operator <<( obj o)
{
    socket << obj;
    return *this;
}
e
template< typename obj> NPC &operator <<( obj o)
{
    return *this;
}
ti permetterebbe di definirti tutte le funzioni in un colpo solo! quelle non
definite per BufferedSocket ti sarebbero segnalate dal compilatore.


<soluzione che mi č venuta in mente adesso e che doveva venirmi in mente
subito pistola che non sono altro>
perchč non usare le template function?
template< typename T> PC &operator <<( T obj)
{
    socket << obj;
    return *this;
}
e
template< typename T> NPC &operator <<( T obj)
{    
    return *this;
}
In questo modo ti definisci tutte le funzioni in un blocco solo, e quelle che non sono definite per 
socket << obj
ti vengono segnalate dal compilatore. Da usare un po' con attenzione, ma mi sembra valga la pena!
<fine autocazziatone>

 socket << obj
Cioč, con una definizione simile, se io poi scrivo
NPC << "ciao" // NPC << (char *)
Lui la passa all'interno automaticamente e senza problemi ?
Massimo

 Massimo
esattamente così... se per caso fai una cosa tipo
NPC << obj;
dove obj è l'istanza di una classe per la quale non hai definito nessun
comportamente in BufferedSocket, il tuo compilatore te lo farà notare, e
potrai porvi rimedio.
--
Posted from [213.82.54.99] 
via Mailgate.ORG Server - 
http://www.Mailgate.ORG

 potrai porvi rimedio.
*BUONO*...
Massimo

 ha scritto nel messaggio
news:8rig6v$q77$1...@news.mclink.it...

Ma perchè volete complicarvi la vita?
La strada più logica da seguire è derivare da uno streambuf.
Manlio Perillo

 La strada più logica da seguire è derivare da uno streambuf.
già, pero hai letto bene lui cosa vuole fare? una classe deve implementare
l'operatore << passando i dati al socket interno, l'altra non deve fare
altro se non restituire *this. Come fai a farlo in un colpo solo? se ci
riesci dillo anche a me, xchè mi servirebbe!
In  ogni caso, non vedo niente di complicato in una template function, anzi,
mi sembra che l'abbiano messa proprio per semplificarsi la vita e non dover
scrivere centinaia di versioni di una stessa funzione!



Ma lui (come scive in un'altro post) vuole anche leggere.
Inoltre in questo modo potrebbe usare solo operator<<
e non write, get, testare lo stato dello stream etc.
Certo, non è strettamente necessario (ma se poi gli dovesse servire?).
Comunque per fare quello che vuole ecco il mio suggerimento:
//
// buffer su socket
//
class socketbuf: public streambuf, public socket
{
    // ...
    protected:
        int overflow(int = EOF);
        int underflow();
        int sync();
};
//
// buffer su nulla (simile a /dev/null in UNIX)
//
class nullbuf: public streambuf
{
    // ...
    protected:
        int overflow(int c = EOF) { return c != EOF ? c : 0; }
        int sync() { return 0; }
};
Quindi ora si ha tutto gratis, restano solo da implementare le classi
per lo stream (banale)
class nullstream: public ostream
{
    privare:
        nullbuf buf;
    public:
        nullstream()
        : ostream(0), buf() { init(&buf); }
        nullbuf* rdbuf() const { return const_cast<nullbuf*>(&buf); }
};
lo stesso per
osocketstream, isocketstream e socketstream.
A questo punto è tutto molto più semplice e lineare
(a mio modo di vedere).
Manlio Perillo

 (a mio modo di vedere).
...mi ricorda il discorso dello sparare a un piccione con un BFG 10K...
Massimo
P.S.
Ho scritto il messaggio alla base di questo thread proprio per spiegare cosa
mi servia e cosa *NON* mi serviva :-)


Mah, fai quello che vuoi. Il programma è tuo.
La libreria di I/O del C++ è stata disegnata per essere facilmente
estensibile, quindi perchè non approfittarne?
Seguendo la via indicata dallo Standard si evita di dover scrivere
codice in più  e di ricorrere a 'stratagemmi'.
In oltre, nel post "Stream" dici che vuoi anche leggere, non solo
scrivere.
Se proprio non vuoi sparare a un piccione con un BFG 10K non
cercare di usare lo stream I/O per 'forza'.
Manlio Perillo

 [cut]
e mi sembra proprio una figata... ti ringrazio, visto che una cosa del
genere serviva anche a me. Purtroppo ho una conoscenza abbastanza limitata
delle classi STL, visto che programmo in C++ seriamente da poco meno di 3
mesi e vengo dall'assembly (che mentalmente è abbastanza opprimente, da un
certo punto di vista). Beh, visto che sono in questo NG x imparare... :-)

 cercare di usare lo stream I/O per 'forza'.
Per l'output mi è comoda l'interfaccia con gli operatori di inserimento.
Ma solo per l'output... per questo volevo un ostream.
Massimo


Anche io fino a meno di un anno fa non capivo (quasi) niente di C++.
Inoltre venivo dal Qbasic;
Se sei interessato a socketstream è disponibile in rete
la libreria GNU Stream++;
comunque l'approccio è diverso da quello suggerito da me.
Se sei interessato a nullstream puoi contattarmi.
Manlio Perillo


Una volta implementato per bene il soketbuf basta fare:
    isocketstream is(/*...*/);
    std::string cmd;
    std::getline(is, cmd);
    if(is.bad()) // errore di trasmissione dati
Più semplice di così!
Comunque voglio precisare una cosa.
Non è detto che derivare uno streambuf è sempre la cosa
migliore oppure che quello che dico sia la cosa giusta.
Io mi sono basato su alcuni punti:
1)  Hai detto che vuoi fare un programma 'C++' in cui ti serve
    gestire i socket.
    Bene, in questo caso ti serve una classe socket implementata
    come si vede.
    In un programma C++ 'vero' usare i socket C oppure la classe
    CAsyncSocket MFC (che non è C++ ma 'C with classes') equivale
    a partire con il piede sbagliato.
    Questa è la parte più difficile dell'I/O; comunque non sei costretto
    a partire da zero. In rete puoi trovare sicuramente qualcosa di già
    fatto e che necessita al più di qualche modifica.
2)  Visto che hai una buona classe socket, implementare un socketbuf è
    molto semplice, ci vogliono un paio di giorni (debug compreso);
    quindi perchè non farlo?
    Inoltre:
3)  Hai detto che per l'output vuoi una interfaccia simile ad ostream.
    In questo caso la soluzione migliore è senz'altro  implementare
    uno streambuf.
4)  Visto che gestire l'input necessita solo l'implementazione di
    underflow, tanto vale farlo.
Spero di essere stato conclusivo.
P.S.
A proposito, ti avevo mandato una e-mail con uno esempio di come
implementare un socketbuf, ti è mai arrivata?
Manlio Perillo

 implementare un socketbuf, ti è mai arrivata?
Temo di no.
Massimo