Newsgroup: it.comp.lang.c++
Titolo: Approfondire MFC

Ciao a tutti quanti! :-)
Per approfondire MFC, libreria della quale ho una conoscienza piuttosto
superficiale, che testo mi consigliate? Diciamo che le mie conoscienze si
limitano a una decina di classi tra cui quelle fondamentali per gestire la
grafica (CRect, CPoint, CDC e derivate) e mi piacerebbe arrivare a
conoscerne un bel pò anche per smettere di ripiegare sulle classi standard
C++
(iostream o addirittura implementazione a basso livello con _read e _write
anziché CFile) per le operazioni che non sono capace a compiere con MFC.
P.S.: C'é qualcuno così generoso da spiegarmi cosa diavolo sono le funzioni
virtuali pure e che scopo possono avere da un lato puramente pratico?

P.S.: C'é qualcuno cosě generoso da spiegarmi cosa diavolo sono le funzioni






virtuali pure e che scopo possono avere da un lato puramente pratico?
Le funzioni virtuali pure consentono di specificare un insieme di operazioni
(semanticamente correlata, se il progetto e' ben fatto) da svolgere su dei
dati dei quali non interessa al momento conoscere nulla.
Tali gruppi di funzioni In java si chiamano interfacce, e nel modello COM
hanno pure il nome di interfacce, e secondo me sono la parte piu' alta
della programmazione/progettazione ad oggetti.
Ad esempio voglio astrarre il concetto che un oggetto debba poter essere
memorizzato su file.
class IStorage {
public:
    void CreatePersistentStorage() = 0;
    void Write() = 0;
    void Read() = 0;
    void FlushStorage() = 0;
    void ClosePersistentStorage() = 0;
}
ed astrarre il concetto che un oggetto possa visualizzarsi
class IView {
public:
    void Prepare() = 0;
    void Draw() = 0;
    vois Finish() = 0;
};
Adesso creo una classe concreta (le precedenti erano classi base astratte)
in cui specifico anche degli instance data su cui operare ed implemento
tutte le funzioni virtuali pure.
 class CTriangle: public IStorage, public IView{
public:
// interface IStorage
    void CreatePersistentStorage() { /* do something*/};
    void Write() { /* do something*/};
    void Read() { /* do something*/};
    void FlushStorage() { /* do something*/};
    void ClosePersistentStorage() { /* do something*/};
// Interface IView
    void Prepare() { /* do something*/};
    void Draw() { /* do something*/};
    vois Finish() { /* do something*/};
private:
    CPoint Vertex[3];
}
adesso posso instanziare degli esemplari di una classe concreta
CTraingle * pMyTraingle = new CTriangle(/*some param*/);
pMyTraingle->CreatePersistentStorage();
pMyTriangle->Write();
pMyTriangle->Draw();
ed applicare solo le operazioni specificare all'inizio della progettazione
in modo completamente astratto.
+-------------------------------+----------------------------+
| name    : Brugiolo Ivan       | C/C++ programming          |
| address : via Conche 59       | NT administration          |
|           35020, Brugine (PD) | Linux user                 |
| phone   : 39+049-9735498      | Windows CE device drivers  |
| email   : 
iv...@iperv.it
      |                            |
+-------------------------------+----------------------------+

virtuali pure e che scopo possono avere da un lato puramente pratico?
Una funzione virtuale e` pura se non e` implementata nella classe che
la dichiara; questa classe e` allora una "base astratta", o, come anche
si dice, una "interfaccia".  Le classi che ereditano da essa si "impegnano
a realizzare" le funzioni virtuali pure che essa dichiara, per poter essere
"classi concrete", cioe` classi di oggetti effettivamente istanziabili.
La programmazione basata su interfacce astratte, e loro implementazioni
concrete in classi separate, e` un approccio che ha moltissimi vantaggi.
Il codice, "cliente" di queste classi, riceve (per puntatore o reference) la
interfaccia, e chiama metodi su di essa, restando cosi` completamente
"disaccoppiato" dalle concrete implementazioni dell'interfaccia stessa.
Meyers, "More Effective C++", raccomanda di non avere mai una classe
concreta che eredita da un'altra classe concreta (consiglio un poco
estremistico, ma Meyers e` un autore fondamentale, e non sono da
trascurarsi i suoi consigli, anche quando comportano un elemento di
"provocazione"... in particolare, consiglio caldamente la lettura di
"More Effective C++", e del predecessore "Effective C++" 2a edizione,
per capire bene i perche` e i percome di questa raccomandazione).
Secondo Meyers, dunque, se mi trovo nella situazione di voler fare:
    class A: public B { // ecc
per due classi concrete A e B, significa che non ho evidenziato bene
la "interfaccia" di B, che voglio garantire che A implementi; consiglia
dunque di identificare questa interfaccia, "raccoglierla a fattor comune"
in una
    class I {
    // funzioni virtuali pure
    };
e avere:
    class B: public I { // ecc
e:
    class A: public I {
        B b;    // eventualmente, per delegare
        // ecc
insomma, separare il riuso di interfaccia, da esprimersi esplicitamente,
da quello, eventuale, di implementazione, da ottenersi per incapsulamento
e delegazione.
Robert Martin arriva a conclusioni simili basandosi sull'analisi delle
dipendenze, specificamente delle "male dipendenze", i casi in cui
un sottosistema piu` stabile si trova a dipendere da uno meno stabile
(il che compromette la stabilita` del primo).  Queste dipendenze sono
caratterizzate da relazioni:
    A -> B
dove la freccia puo` indicare, non solo eredita` (il "caso peggiore"!),
ma anche rapporto di "clientela" -- A "dipende-da" B perche` il codice
di A _usa_ classi definite in B.  Se B cambia in continuazione, devo
dunque continuamente ricompilare (testare, ecc) e rilasciare nuovamente
A, per cui la sua "stabilita`" sparisce anche se non tocco una virgola del
codice di A.
Soluzione: "inversione delle dipendenze", nella terminologia di Martin;
identificare le _interfacce_ che le classi di B espongono/implementano,
e quelle di A usano, e sostituire la mala dipendenza A->B con due
dipendenze buone:
    A -> I    // A dipende dall'interfaccia I perche` la usa
    B -> I    // B dipende dall'interfaccia I perche` la implementa
I, essendo "pura interfaccia" (collezione di funzioni virtuali pure), puo`
assai piu` facilmente essere "stabilizzato", per cui tutte le dipendenze
vanno nel verso GIUSTO, dal meno stabile al piu` stabile.
Questo discorso non e` dunque troppo diverso anche se condotto a livello
di granularita` di "componente", o "categoria", o "sottosistema" che dir
si voglia, invece che a quello piu` atomizzato della "classe"; anche per
realizzare l'inversione di dipendenza alla Martin, l'uso di funzioni
virtuali
pure (e basi astratte) e` la chiave di volta.
Vediamo un esempio pratico, benche`, necessariamente, "giocattolo" nella
modestia delle sue dimensioni: come implementare la persistenza degli
oggetti in modo indipendente dal medium su cui persistono (file, database,
connessione in rete...).  Si identificano chiaramente, al massimo livello di
semplicita`, due classi astratte d'interfaccia:
    class Medium {
    public:
        virtual bool scrivi(void*, size_t) = 0;
        virtual size_t leggi(void*, size_t) = 0;
    };
    class Persistente {
    public:
        virtual bool salva(Medium&) = 0;
        virtual bool carica(Medium&) = 0;
    };
Una classe che deve potere "persistere" eredita da Persistente, e
implementa salva e carica come appropriato, basandosi sulla
astratta possibilita`, che Medium offre, di leggere e scrivere dei
byte; le implementazioni concrete di Medium non devono cosi`
preoccuparsi di "come" si salva ogni specifica classe, solo di come
leggere e scrivere byte verso una data destinazione (file, data
base, eccetera).
E` chiaro che nulla si puo` implementare "in assoluto", ne`
dei metodi salva e carica di Medium, ne` di quelli leggi e scrivi
di Persistente; per questo, dunque, devono logicamente essere
"puri" -- pura _interfaccia_, senza aspetti implementativi.
Alex