Newsgroup: it.comp.lang.c++
Titolo: Liste d'inizializzazione

IMHO, avrebbero dovuto limitare tali liste all'iniz. delle classi base.
Inoltre, date le limitazioni, è impossibile inizializzare tutto quello 
che si vuole e così diventano soltanto una palla al piede.
Kiuhnm


Non ho capito niente.
Elabora, please.

 Elabora, please.
Più che elabora, io direi espandi queste macro :-)


ROTFL

 che si vuole e così diventano soltanto una palla al piede.
Cosa succede se "inizializzo" tutte le variabili membro nel corpo del 
costruttore?
Kiuhnm


Non si può inizializzarle nel "corpo" del costruttore. Si può solo 
assegnarle. L'inizializzazione, per definizione, avviene solo quando 
inizia la lifetime. Quindi, nel caso di membri di una classe, prima che 
venga eseguito il corpo del costruttore.
E' a questo che servono le liste di inizializzazione.

 E' a questo che servono le liste di inizializzazione.
Ed è per questo che ho utilizzato le virgolette.
Cosa fare allora se le inizializzazioni sono interdipendenti?
Per es. come ci si comporta se la seconda inizializzazione deve avvenire 
solo qualora la prima abbia successo? Ci diamo alla programmazione 
funzionale?
Kiuhnm


No. Si rivede il design perchè c'è qualche cosa che non funziona.
La logica di un oggetto è che o esiste o non esiste.
Nel momento in cui si crea un oggetto tutte le condizioni affinché 
l'oggetto possa esistere devono sussistere (e teoricamente questo deve 
essere verificato PRIMA che l'oggetto venga creato).
Se qualcuno cerca di creare un oggetto senza che tutte le condizioni 
necessarie siano soddisfatte è legittimo che il costruttore lanci 
un'eccezione (anzi: è auspicabile).
Se le condizioni necessarie per la creazione dell'oggetto non sono 
conoscibili dal cliente della classe, c'è qualche cosa che non va nel 
design del sistema.
Difficile dire di più, senza un esempio specifico di cosa ti crea problemi.

 design del sistema.
E' possibile conoscere le condizioni necessarie, ma non se tali 
condizioni saranno soddisfatte durante l'inizializzazione.
Per es. un costruttore potrebbe richiedere e aspettare l'input dell'utente.
Kiuhnm

 Per es. un costruttore potrebbe richiedere e aspettare l'input dell'utente.
No.
Se l'esistenza dell'oggetto richiede informazioni che l'utente fornisce 
interativamente, tali informazioni devono essere raccolte prima della 
creazione dell'oggetto.

 puntatori su 10.
Prima dell'oggetto in questione crei un oggetto "ballon d'essai" che 
soddisfa le condizioni minime: se ce la fai crei l'oggetto vero e 
distruggi il palloncino, se no amen...


O meglio ancora utilizzi un sistema di gestione della memoria che è 
interrogabile.
Questo se hai un sistema nel quale, per vari motivi, non puoi usare le 
eccezioni.
La mancanza di memoria disponibile non è una condizione interna (ovvero 
facente parte della logica del programma) ma esterna e legata 
all'ambiente di runtime e non all'environment (ovvero non dipende 
dall'utente o dallo stato del dominio del problema, ma piuttosto da 
condizioni del sistema in cui il Sw viene eseguito).
In altre parole è un pessimo esempio perché tranne alcuni casi 
particolari (tipo SW di sistema e/o SW estremamente critico) se non c'è 
memoria per allocare tre puntatori è molto probabile che l'unica cosa 
sensata da fare sia terminare l'esecuzione punto e stop.

 Kiuhnm
Mi intrufolo nel post perchè ho anche io questo dubbio, e in
particolare legato ad un caso concreto. Ho una classe che si occupa di
mantenere un log, scritto giorno per giorno in file del formato
gg-mm-aaaa.log, attraverso un ofstream, che ho reso membro di tale
classe. Mi piacerebbe poter decidere il formato del file di log
all'interno della classe, ma così facendo non posso aprire l'ofstream
nella lista di inizializzazione. Se invece volessi fare in quel modo,
dovrei rimandare la scelta del formato all'ambiente che la inizializza,
ma non sono convinto della bontà di questa soluzione (perchè ho
l'impressione di rendere l'implementazione della classe nota
all'ambiente chiamante). Quale sarebbe il design ottimale per i miei
scopi?

 scopi?
dacci quello che hai :)
-- 
sbi

 scopi?
Non e' obbligatorio aprire il "fstream" nell'inizializzazione.
Lo si puo' creare senza inizializzarlo, in quanto il suo costruttore di 
default lo inizializza automaticamente come stream chiuso.
Poi, lo apri con la "open" nel costruttore, oppure in una funzione 
membro (che potrebbe chiamarsi "open").
Se lo apri nel costruttore e l'apertura fallisce devi sollevare 
un'eccezione, oppure permettere al codice utilizzatore di testare se il 
file e' aperto.
In conclusione, penso devi scegliere tra tre strategie:
* Per tutta la vita dell'oggetto che gestisce il log, esiste uno stream 
aperto sul log. Se creando l'oggetto non si riesce ad aprire il file, si 
solleva un'eccezione in quanto l'oggetto non e' valido. Il file viene 
chiuso esclusivamente nel distruttore.
* Durante la vita dell'oggetto che gestisce il log, esiste uno stream 
sul log, che puo' essere aperto o chiuso. Se creando l'oggetto si passa 
il nome di un file, viene tentata l'apertura; se viene passato un 
puntatore nullo non viene tentata. Vengono pubblicate funzioni per 
aprire e chiudere il file, e per testare se il file e' aperto. Il file 
viene chiuso anche dal distruttore.
-- 
     Carlo Milanesi
 
http://digilander.libero.it/carlmila

 creazione dell'oggetto.
Ricorda che sto criticando una scelta di design del linguaggio.
Il tuo "devono" è dettato semplicemente dal fatto che altrimenti si ha 
un problema.
Ti sto dicendo che è proprio questo fatto ad annoiarmi.
ODIO LE LISTE D'INIZIALIZZAZIONE. Il programmatore dovrebbe essere 
libero d'inizializzare tutto a modo suo (a parte forse, e dico /forse/, 
le classi base).
Il risultato finale è che il codice è leggermente inefficiente perché 
avvengono inizializzazioni inutili.
Impedirle richiederebbe programmare in modo innaturale e quindi rovinare 
le astrazioni.
Kiuhnm

 distruggi il palloncino, se no amen...
Ma il C++ non doveva servire a produrre software efficiente?
La soluzione più naturale è quella di fare tutto il lavoro nel corpo del 
costruttore e amen.
Il problema è che le inizializzazioni automatiche sono indesiderate ed 
andrebbero evitatate per questione d'efficienza.
Il linguaggio D, ora che ci penso, si è sbarazzato delle liste 
d'inizializzazione:
http://www.digitalmars.com/d/class.html
Kiuhnm

 sensata da fare sia terminare l'esecuzione punto e stop.
Per me l'esempio è legittimo, ma posso venirti incontro senza problemi.
Supponi che l'utente passi come parametro il numero di puntatori che 
devono essere inizializzati.
Kiuhnm

 un problema.
No. E' vero il contrario: la scelta di design del linguaggio è dovuta 
proprio al fatto che le liste di inizializzazione si applicano per 
l'inizializzazione di oggetti la cui esistenza sta iniziando e che 
pertanto o è garantita o una eccezione è dovuta.
E' una questione di design.
Non conoscendo quale sia il codice che ti da problemi è difficile dire 
di più ma, per esperienza, posso dirti questo:
molti programmatori C++ (sopratttutto non troppo esperti, ma a volte 
anche quelli con esperienza) cercano di usare la costruzione di un 
oggetto laddove sarebbe opportuna una richiesta di istanziazione.
Sono due concetti differenti.
Nel secondo caso l'uso diretto di un costruttore è opportuno (non 
obbligatorio).
Nel primo caso non lo è mai: si usa un membro statico o un membro di 
un'altra classe che si occuperà di raccogliere quannto necessita 
all'istanziazione, eseguirà l'istanziazione e restituirà un puntatore 
all'istanza (in cass di errore genera un'eccezione o restituisce NULL).
Una cosa che dovrebbe renderti sospettoso è la tua affermazione che le 
liste di inizializzazione dovrebbero applicarsi solo alle classi base. 
Non ha senso dato che, in termini di inizializzazione, non c'è alcuna 
differenza fra le classi base e i membri dati.


Intendi allocati?
Se non vi è memoria sufficiente, una eccezione è dovuta.
Se sei in un ambiente in cui le eccezioni non possono essere usate, tale 
ambiente dovrebbe fornire una API per la verifica a priori dello stato 
della memoria.
Se il modo in cui la classe alloca la memoria è parte 
dell'incapsulamento della classe e non è conoscibile dall'utente *e* la 
classe è disegnata per funzionare in un environmente in cui non si 
possono usare le eccezioni allora la classe stessa avrà i costruttori 
privati (o protetti) e quindi il problema non si pone dato che l'utente 
non li può chiamare.

 d'inizializzazione:
Falso. Il linguaggio D usa una terminologia differente da quella del C++.
Il linguaggio D si è sbarazzato dei costruttori, non solo delle liste di 
inizializzaione.
O meglio usa i costruttori (intesi come nel C++) ma non ti permette di 
definirli in alcun modo: li definisce e gestisce lui.
Quelli che in D vengono chiamati costruttori sono in realtà normali 
metodi che vengono richiamati dopo la costruzione e l'inizializzazione 
(intese come nella terminologia del C++).
Puoi fare in C++ quello che fa il D semplicemente scrivendo costruttori 
che chiamano una funzione init e scrivendo init per ogni classe nel modo 
in cui scriveresti quello che D chiama costruttore.
Vi sono cose che puoi fare in C++ e non NON puoi fare in D. Immaginati 
una classe istanziata ad un indirizzo specifico (con un placement new) 
nel costruttore della quale vuoi controllo completo sull'accesso alla 
memoria (che potrebbe essere mappata ad un dispositivo hardware). NON 
VUOI che la memoria venga inizializzata a 0 e poi riassegnata.

 differenza fra le classi base e i membri dati.
Così ha voluto l'autore. Concettualmente c'è una bella differenza tra 
classe base e membro. Ecco perché ritengo che /forse/ (v. frase 
originale) sarebbe opportuno distinguere i due casi.
Kiuhnm

 Intendi allocati?
Intendo il numero di puntatori che dovranno essere inizializzati nel 
costruttore. In altre parole, lo schema d'inizializzazione può dipendere 
dai parametri e le liste d'inizializzazione sono troppo limitate.
Kiuhnm

 (intese come nella terminologia del C++).
Vero. Ho perso un pezzo:
"Members are always initialized to the default initializer for their 
type, which is usually 0 for integer types and NAN for floating point 
types. This eliminates an entire class of obscure problems that come 
from neglecting to initialize a member in one of the constructors."
Kiuhnm

 originale) sarebbe opportuno distinguere i due casi.
Non in termini di inizializzazione.
Inizializzazione, in C++, significa definire il valore iniziale dei 
componenti della classe (membri dati e classi base) in modo che la 
classe sia valida. Non definire il valore iniziale dell'istanza della 
classe.
Esiste un'altro significato per inizializzazione: dare alla classe uno 
stato iniziale utile nell'ambito in cui viene utilizzata. Ovvero 
definire il valore iniziale dell'istanza.
In alcuni casi (ma non in tutti) le due cose si possono fare 
contemporaneamente e pertanto è possibile usare un costruttore per farle 
entrambe.
In altri casi la definizione del valore iniziale dell'istanza non può 
essere fatta contemporaneamente all'inizializzazione dell'istanza stessa.
In altri casi ancora vale il contrario: l'inizializzazione della classe 
può essere fatta SOLO assegnandole un valore iniziale.
In C++ puoi gestire tutti i casi, ma non tutti vanno gestiti con un 
costruttore.

 altro valore.
Il problema è inesistente. Se la cosa è inutile il compilatore può 
benissimo ottimizzare il codice e non fare la doppia assegnazione.
Nota però che nel momento in cui il corpo del costruttore viene eseguito 
la regola dice che TUTTI i componenti della classe (membri e 
sottoclassi) sono già stati inizializzati. Quella che non è ancora 
inizializzata è l'istanza della classe stessa.
Come si suol dire: il tutto è più che la somma delle parti. Il fatto che 
le parti siano inizializzate non implica che lo sia il tutto.

 funzionale?
Fattorizziamo la classe in modo che la prima inizializzazione avvenga
in una classe base, in attesa che al linguaggio vengano aggiunti i
costruttori concatenati.
Ciao,
Nicola

 Per es. un costruttore potrebbe richiedere e aspettare l'input dell'utente.
Questo mi pare un errore di disegno. Non e' cnhe la logica del
programma debba per forza essere tutta infilata nei costruttori.
Ciao,
Nicola

 costruttore.
class A
{
   int i[2];
};
Come crei un'istanza di A con i[] = {a,b} con a e b scelti dall'utente?
Kiuhnm

 le parti siano inizializzate non implica che lo sia il tutto.
Se così non è, allora avviene una doppia inizializzazione.
La struttura del C++ presenta in pratica lo stesso problema che porta al 
flickering nelle interfacce grafiche poco accorte.
Kiuhnm

 Questo mi pare un errore di disegno.
Non può che essere altrimenti, da buon programmatore di C++.
Ma ritengo che un /ottimo/ linguaggio multi-paradigma non dovrebbe 
costringere il programmatore a rivedere le proprie astrazioni, se 
concettualmente corrette.
Kiuhnm

 costruttori concatenati.
Diminuendo di gran lunga la coesione.
Pensa a questo metodo usato per n > 2.
Kiuhnm


Ma io non sono convinto che sia concettualmente corretto che la
costruzione dei sotto-oggetti sia sempre parte della costruzione
dell'oggetto complessivo e per questo non trovo niente di male nel
fatto che la costruzione di un oggetto complesso avvenga in fasi
distinte, che a volte sono separate in diversi elementi di una
gerarchia di classi, mentre altre volte vengono esplicitamente
coordinate da una fabbrica esterna.
Non solo, ma possono esistere diversi livelli di astrazione e quello
che appare atomico ad un livello puo' benissimo non esserlo ad un
altro.
Il fatto che le inizializzazioni di membri/basi diverse non possano
"interferire" tra loro e' certamente una limitazione, ma da una parte
non ho mai visto una proposta migliore, dall'altra l'esigenza di farlo
vale comunque come campanello di allarme circa l'eccessiva complessita'
delle astrazioni in gioco.
Quello che e' pernicioso e' il punto di vista mutuato dagli albori
dell'analisi object oriented che ad un'astrazione del dominio debba per
forza corrispondere una e una sola classe nel codice, quando alla fine
della fiera le classi, soprattutto in C++, non sono che delle specie di
super sequence point.
Altro aspetto importante e' che se si accetta il punto di vista che e'
bene separare il codice che effettivamente modella entita' del dominio
da quello che e' puro macchinario, questo genere di problema si pone
piu' raramente. Nella mia esperienza il codice che porta le classi di
modello a raggiungere il proprio stato iniziale corretto e' in buona
parte, per l'appunto, macchinario, infrastruttura.
Ciao,
Nicola


Se le classi in gioco sono molto complesse mi pare meglio individuare,
se e' possibile, delle sotto partizioni, piuttosto che puntare a
salvaguardare la coesione a tutti i costi.
Le classi che rappresentano i concetti fondamentali su cui lavoro hanno
la loro implementazione ripartita lungo una gerarchia di 7 classi e la
loro interfaccia astratta e' ripartita su una gerarchia di altre 7
classi. L'eredita' multipla e' utilizzata in diversi casi per cui il
totale delle classi base supera di gran lunga le 14 che risultano qui.
Ripartissi ora probabilmente eliminerei una o due delle classi di
implementazione utilizzando policy piu' decisamente; le classi di
interfaccia invece sostanzialmente servono tutte, perche' rappresentano
i diversi gradi di dettaglio vs. genericita' ai quali vengono usati gli
oggetti in questione.
La somiglianza dell'altezza delle due gerarchie non e' un caso, perche'
facilmente ad un grado di dettaglio dell'interfaccia corrisponde un
blocco di funzionalita' concreta coesa. La separazione tra la gerarchia
astratta, polimorfica, e quella concreta, tendenzialmente generica,
consente di ovviare ai problemi di accoppiamento causati in genere
dall'utilizzo dell'eredita' come meccanismo per il riuso
dell'implementazione.
Ciao,
Nicola

 Altro aspetto importante e' che se si accetta il punto di vista che e'
...
Ecco, lo sapevo che il buon Nicola (stranamente silente su questo thread 
ultimamente) avrebbe saputo esprimere in maniera chiara la cosa.
Sottoscrivo!
(chissà perché c'è in giro la mania di dire "quoto" invece che 
"sottoscrivo" quando "quoto" significa tutt'altro).


La domanda non ha senso.
O meglio, lo ha se stiamo parlando di come funziona il linguaggio (per 
esempio se dobbiamo scrivere un compilatore C++). Ma non se parliamo di 
come si USA il linguaggio.
La classe suddetta non ha alcun senso in quanto praticamente non ha una 
interfaccia pubblica.
Inoltre anche nel caso, simile:
class A {
public:
   // some public members
private:
   int i[2];
};
la domanda non dimostra nulla perché non hai spiegato per quale motivo 
non è applicabile il meccanismo ovvio di inizializzare il vettore per 
default e poi assegnargli i due valori e non hai spiegato per quale 
motivo sei obbligato ad una implementazione con "int i[2];" invece che 
con "int i0, i1;".
Nota che dire che in questo modo avvengono due inizializzazioni non è 
una risposta valida per due motivi:
1) cosa non va nel fatto che avvengano due inizializzazioni (o tre o 
quattro) ?
2) non è detto che avvengano due inizializzazioni: il compilatore può 
ottimizzare, se vuole.
La critica che dice che affidarsi all'ottimizzazione del compilatore 
(che non è garantita) non è sufficiente e il linguaggio dovrebbe 
permettere di esprimere la cosa in maniera univoca è una critica che 
lascia il tempo che trova.
Se questo è il livello di controllo di cui necessiti allora l'unica 
risposta possibile è: usa il C o l'assembler.
E' una critica simile alla vecchia critica che i detrattori del C++ (e 
difensori del C) facevano dicendo che:
struct Base {
   virtual void foo();
   // ...
};
struct Derived: public Base {
   virtual void foo();
   // ...
};
void bar()
{
   Derived d;
   // ...
   Base *p=&d;
   // ... no change to p here
   p->foo(); // possible indirect call
   // ...
}
la chiamata a foo tramite un puntatore a Base è nominalmente una 
chiamata indiretta (tramite la virtual table). Ma un buon compilatore è 
in grado di verificare con analisi statica a compile time che p di fatto 
punta a un Derived e quindi può codificare una chiamata diretta a 
Derived::foo. O addirittura implementare la funziona inline.
Sostenere che questa è una debolezza del C++ (e c'è chi ha sostenuto 
cose come questa) è una tesi che lascia il tempo che trova. La risposta 
è sempre quella:
1) chi se ne frega
2) il compilatore può ottimizzare
se non ti vanno bene ne 1 ne 2, allora usa pure il C o l'assembler.

 Kiuhnm

 interfaccia pubblica.
Effettivamente non sono stato chiaro. L'interfaccia pubblica sei 
autorizzato a crearla te (costruttori, ecc...).
Kiuhnm

 il senso.
Non potevi: ho dimenticato di dire che vi chiedo semplicemente di 
aggiungere l'interfaccia pubblica alla classe :)
Kiuhnm

 parte, per l'appunto, macchinario, infrastruttura.
Questa è una visione che condivido solo in parte. Anche il macchinario 
deve a sua volta essere suddiviso in astrazione e macchinario.
Per es. tu sei un semplice macchinario (senza offesa) per la società, ma 
non per te stesso o per noi altri macchinari.
Kiuhnm

 delle limitazioni.
"Language shapes the way we think, and determines what we can think
about." -- Benjamin Lee Whorf
-- 
sbi

 salvaguardare la coesione a tutti i costi.
[...]
Ti rendi conto che in C tutto questo può essere evitato scrivendo 50 
righe di codice preservando totalmente la coesione?
Continuo a sostenere che i costruttori, e in particolare la lista 
d'inizializzazione, dovrebbero essere rivisti.
In un post precedente, AnalogFile ha parlato delle proprietà emergenti, 
anche se in altri termini. Come un organismo non è dato dalla somma 
"diretta" delle parti, così un'entità non può essere inizializzata 
semplicemente inizializzandone le parti in fasi separate. E' opportuno 
che ci sia l'"interferenza" di cui parlavi in precedenza (interferenza 
che gli scienziati non sono ancora riusciti a ricreare in laboratorio).
Voler mantenere il codice C++ quasi completamente isomorfo all'assembly 
(o al C) ha portato a un linguaggio molto complesso che ha dovuto 
superare alcune sue limitazioni aggiungendo strumenti ancora più 
complessi (vedi template, in primis).
Kiuhnm


Confondo le acque fino all'uscita del prossimo standard:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1824.htm
;-)
Tra parentesi l'inverso e' altrettanto interessante, se non di piu':
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2003/n1509.pdf
Ciao,
Nicola

 about." -- Benjamin Lee Whorf
Ecco perché il vero programmatore non pensa in C, C++, asm, Ocaml, 
Scheme, Lisp, ecc..., ma in linguaggio matematico, sempre in continua 
evoluzione.
Kiuhnm


...che e' il motivo per cui preferisco programmare in C++ o, al limite,
Python.
Ciao,
Nicola

Kiuhnm ha scritto:

E' un assioma? :)
-- 
sbi

 Python.
cosě eviti cattivi pensieri? :)
mi verrebbe una battuta riguardo i serial killer ed il COBOL...
-- 
sbi

 non per te stesso o per noi altri macchinari.
C'e' pero' un problema di differenza di pubblico. Il contesto al quale
mi riferisco come esempio e' la separazione tra interfaccia e
implementazione al confine tra un sottosistema ed un altro.
Presumibilmente un'interfaccia ben disegnata nasconde dettagli tecnici
ad un livello sufficiente da permettere di non avere personale
competente in quei dettagli tecnici tra chi utilizza l'interfaccia. Per
esempio chi utilizza l'interfaccia del sottosistema "database" potrebbe
(dovrebbe!!) non aver bisogno di saper niente di SQL.
La tua obiezione mi pare invece rivolta all'interno del sottosistema
dove, se e' in genere bene comunque compartimentalizzare le attivita',
l'information hiding ha in genere un ruolo meno drastico.
L'effetto che mi par di notare e' che sebbene il macchinario sia in
parte proprio rivolto ad ovviare ai problemi che descrivi, visto
all'interno del suo contesto lo stesso genere di problemi si presenta
meno di frequente, magari anche solo perche' la sequenza di operazioni
che si cerca di implementare sparsa su diversi costruttori diventa
esplicita.
Probabilmente sono influenzato dal contesto in cui opero. Per me e'
naturale operare con oggetti complessi per i quali la vera "nascita" e'
l'uscita dalla classe fabbrica. In questo caso l'alternativa tra
precostruzione dei membri e passaggio al costruttore, richiamo dei
costruttori dei membri dal costruttore dell'oggetto principale e
inizializzazione multifase risulta meno significativa, salvo che
l'ultima alternativa complica la gestione della consistenza all'interno
dell'oggetto.
Ciao,
Nicola

 così eviti cattivi pensieri? :)
No, perche', almeno all'interno del paradigma imperativo non pongono
limiti a cio' che posso pensare, a differenza ad esempio di Java.
Ciao,
Nicola

 complessi (vedi template, in primis).
Hai gia' la lista di inizializzazione ed il corpo del costruttore, e la
possibilita' di combinarne di piu' insieme. Se tutto va bene il
prossimo standard avra' anche la possibilita' di concatenare piu'
costruttori della stessa classe. Francamente alternative migliori non
mi vengono in mente, salvo la considerazione che il C++ lascia troppa
micro-ottimizzazione, vera o presunta, in mano al programmatore, quando
forse sarebbe meglio un linguaggio un po' piu' grossolano, ma piu'
facile da compilare.
Ciao,
Nicola

 limiti a cio' che posso pensare, a differenza ad esempio di Java.
E' una scelta che considererei valida a patto non venga fatta a priori,
al di fuori del contesto del problema da risolvere. D'altra parte
all'atto della scelta credo sia naturale avere predilezione per alcuni
linguaggi( quelli in cui si č investito :) ).
Personalmente trovo Java abbia la sua ragione di esistere in
determinati ambiti.. ma credo la discussione sia OT qui :)
-- 
sbi

 limiti a cio' che posso pensare, a differenza ad esempio di Java.
Non è del tutto vero, ma non importa.

 E chi ti impedisce di continuare cosi', allora?
L'inizializzazione automatica.
Kiuhnm

 dell'oggetto.
Ok.
Kiuhnm


Sono curioso, fai un esempio.
Ciao,
Nicola


Pensa ad un classico automa a stati.
Vi sono molti modi di progettarlo e di realizzarlo. Ma ce n'è uno che fa 
lievemente a botte con il C++ (non che sia per forza il design migliore, 
ma è un design possibile, sebbene non implementabile *direttamente* in C++).
L'idea è qualla di avere un oggetto (istanza) la cui classe può cambiare 
a runtime.
Nel caso dell'automa hai una classe per ogni stato. Se l'automa non è 
semplicemente un parser o qualche cosa del genere ma rappresenta 
l'evoluzione degli stati di un sistema che risponde a stimoli esterni in 
maniera dipendente da uno "stato" (che può variare a seconda di uno 
stimolo o in base ad altri meccanismi, non importa), questo tipo di 
modellazione è utile e facile da capire per il domain expert.
In pratica il modello ha una classe per stato e ciascuna classe ha come 
metodi i possibili stimoli esterni.
Naturalmente non è l'unico modo di modellare la cosa. Naturalmente puoi 
modellare in quel modo e poi durante il design "arricchire" il modello 
con un proxy (aggiungendo dettagli implementativi ed un livello di 
indirezione). Inoltre se l'oggetto ha uno stato complesso ti tocca 
aggiungere anche un livello di indirezione verso lo stato e modellare 
quest'ultimo separatamente come elemento aggregato, oppure passare per 
l'intero ciclo di copia/distruzione ad ogni cambio classe.
Ma sono tutte complicazioni. A questa stregua si può dire che neppure 
Java (o assembler, se per questo) ti limitano il modo di pensare: basta 
poi aggiungere meccanismi per "sintonizzare" il design di processo con 
il design d'implementazione.
Altri punti di vista che tendono a non essere immediatamente "colti" se 
stai "pensando C++" sono quelli che riguardano i sistemi estremamente 
introspettivi e/o estremamente dinamici (fin'anche alla sintesi a 
runtime di una classe o di un metodo).
E nota che sono restato in un paradigma imperativo (e a oggetti). 
Giustamente tu stesso lo hai specificato. Il che è ovvio perché se 
scivoliamo nel dichiarativo o nel funzionale puro le "possibilità di 
pensiero" si allargano ulteriormente. E parecchio.

 il costruttore virtuale "type()".
E' la stessa cosa. Puoi considerare chiamato quel costruttore.
Anzi: è più corretto pensare che è stato chiamato. Solo che l'unica cosa 
che fa è costruire un tipo built-in senza cambiare neanche un bit della 
sua rappresentazione in memoria. Ma lo costruisce nel senso che prima 
della sua esecuzione accedere al built-in è undefined, mentre dopo è ok.
Il resto è questione di implementazione e ottimizzazione: il suddetto 
costruttore viene chiamato ma la chiamata è implementata inline e genera 
0 istruzioni assembler.

 E' la stessa cosa. Puoi considerare chiamato quel costruttore.
No, il vero costruttore inizializza il valore (per es. "int()" è 
equivalente a "int(0)").
Kiuhnm

 runtime di una classe o di un metodo).
Su questo sono d'accordo. D'altra parte non puoi avere dinamismo
estremo senza rinunciare a qualche vantaggio della staticita'.
Ciao,
Nicola

 Sono curioso, fai un esempio.
Un difetto del C++ (e del 99% dei linguaggi) è che non è un 
meta-linguaggio per sé stesso.
Penso che il linguaggio più libero ed espressivo in assoluto sia Lisp.
Kiuhnm


No. Non è la stessa cosa.
class Automaton {
public:
   virtual void stimulusA()=0;
   virtual void stimulusB()=0;
   virtual void stimulusC()=0;
   // etc
protected:
   // complex internal
   // data structures
};
class InitialState {
public:
   virtual void stimulusA();
   virtual void stimulusB();
   virtual void stimulusC();
   // etc
};
class StateA {
public:
   virtual void stimulusA();
   virtual void stimulusB();
   virtual void stimulusC();
   // etc
};
class StateB {
public:
   virtual void stimulusA();
   virtual void stimulusB();
   virtual void stimulusC();
   // etc
};
// more states
// client code:
...
InitialState automaton;
automaton->stimulusA();
automaton->stimulusB();
...
Secondo il modello, alcune stimoli possono cambiare la classe dell'oggetto.
Ovviamente PUOI farlo in C++. Ma devi aggiungere meccanismi.
Un proxy che implementa l'interfaccia di Automaton e mantiene un 
puntatore, ridefinire gli stimoli nelle classi stato affinchè ritornino 
una classe stato nuova se vi è un cambio di stato, costruttori non 
banali in Automaton per copiare la struttura interna OPPURE una classe 
che rappresenta la struttura e codice per accedere ad essa in via 
indiretta. etc.
Si può fare. Ma NON viene naturale in C++. Come dicevamo il problema è 
la mancanza di "supporto diretto".
E naturalmente questo è UN esempio. In generale, in C++ non viene 
naturale pensare che la classe di un oggetto possa cambiare durante la 
sua lifetime.
Il che implica che "pensando C++" questo tipo di struttura o non viene 
naturale o implica un maggior numero di risorse mentali impegnate e di 
conseguenza meno visione del sistema (il solito discorso sulla quantità 
di concetti che riusciamo a "visualizzare" mentalmente in contemporanea).
Per questo esiste il concetto di astrazione. Ma generalmente "pensare 
C++" (o "pensare Java" o altro) implica comunque un qualche tipo di 
"orientamento" della mente. Il che peraltro è quasi inevitabile.
Per questo dissi che la tua affermazione non era del tutto vera.
E per questo dissi che in realtà la cosa non importava.
Tra parentesi: il problema in realtà esiste in buona misura anche in 
Smalltalk e Python. Ma molto meno in ambienti OO basati su linguaggi 
funzionali (indipendentemente dal paradigma funzionale ivi possibile) e 
in linguaggi come Ruby (che deriva da Smalltalk, ma è molto più dinamico).


In generale i linguaggi funzionali che non insistono ad essere 
funzionali puri (ovvero ammettono il rebinding di variabili) sono più 
liberi ed espressivi poiché ammettono sia dati che programmi come first 
class entities.
Ma Ruby non è funzionale eppure è MOLTO libero ed espressivo. Anche se, 
secondo me, potrebbe beneficiare di un maggiore supporto al paradigma 
funzionale puro e al paradigma dichiarativo (dopodiché però diverrebbe 
un linguaggio complesso invece che semplice. Purtroppo c'è sempre da 
mediare fra caratteristiche contrastanti).

 Ma Ruby non è funzionale eppure è MOLTO libero ed espressivo. Anche se, 
Lisp /non/ è funzionale, ma multi-paradigma e di gran lunga superiore a 
Ruby, tecnicamente parlando.
Perché Lisp ha avuto così poco successo allora? Per il semplice fatto 
che non ha praticamente sintassi e i programmatori non si sentono a 
proprio agio. Peccato che le sue proprietà derivino proprio da questo fatto.
Kiuhnm

 [...] Ruby (che deriva da Smalltalk, ma è molto più dinamico).
mh?
-- 
sbi


Cosa ti perplime?
E' più "dinamico" nel senso di gestione a runtime dei metadati, ovviamente.

 fatto.
Non è detto.
Si diceva che la superiorità di Smalltalk era dovuta in parte alla sua 
sintassi. Ma che proprio questa sintassi fosse uno dei motivi per cui 
non piaceva molto ai programmatori.
In parte Python e in toto Ruby dimostrano che non è proprio così.
Forse in futuro avremo un linguaggio con la potenza di Lisp ma con una 
sintassi. Anzi: con una sintassi semplice ed efficace.

 E' più "dinamico" nel senso di gestione a runtime dei metadati, ovviamente.
ovviamente togliendo l'"ovviamente".. in cosa è più dinamico nella 
"gestione a runtime dei metadati"?
-- 
sbi

 sintassi. Anzi: con una sintassi semplice ed efficace.
Da questo punto di vista il mio linguaggio è piuttosto originale perché 
elimina il problema alla radice. Tra un anno dovrei avere qualcosa di 
concreto da mostrare.
Kiuhnm


Puoi in Smalltalk creare un'istanza di un'oggetto, e poi creare un nuovo 
metodo a cui quell'oggetto risponde e "attaccarglielo" in runtime.
Ovvero di fatto puoi prendere un'oggetto e senza sapere la sua classe 
creare a runtime una classe derivata da questa, aggiungere dei metodi e 
infine cambiare la classe dell'oggetto in questione in modo che diventi 
quest'ultima e non quella che era quando lo hai creato?
Nel vecchio Smalltalk non era semplice una cosa del genere (ammesso che 
si potesse). Ma ammetto di non "giocare" con smalltalk da molti anni.

 concreto da mostrare.
Sono curioso.
Saresti disposto a seguire il principio FOSS e cominciare con il 
pubblicare dei documenti anche solo parziali?
Potresti creare un Wiki o un Blog. In cambio otterresti certamente 
l'interesse e la collaborazione di qualcuno (a gratis) e probabilmente 
un utile confronto (sopratutto se te la senti di pubblicare in inglese).

 pubblicare dei documenti anche solo parziali?
No, no, è davvero troppo presto. Il 90% delle mie energie sono 
attualmente rivolte all'OS, e lo saranno ancora per parecchio tempo.
Trovo a malapena il tempo di partecipare a questo ng.
Kiuhnm

 sua lifetime.
amfatti.
ciao
Soviet


 si potesse). Ma ammetto di non "giocare" con smalltalk da molti anni.
become, perform:, doesNotUnderstand: e compile:, compile: classified:
esistevano già nell'80 .. quindi direi si a tutto.
-- 
sbi


C'è una bella differenza fra il poter fare, l'emulare, e il supportare 
nativamente.
Tra l'altro siamo in un subthread (che sta andando sempre + OT) in cui 
si parlava del fatto che un linguaggio può influenzare il modo di pensare.
Ora, da quel che capisco, la tua idea è di usare l'introspezione per 
identificare la classe di un oggetto. Creare una stringa contenente il 
codice che definisce una classe derivata da questa, compilare quel 
codice (per chi non lo sapesse in smalltalk il compilatore fa parte del 
runtime del linguaggio), istanziare la classe così creata e infine usare 
"become:" per "swappare" le identità del vecchio oggetto e di quello nuovo.
Potrebbe funzionare. Non sono del tutto sicuro di come ti comporteresti 
nel caso la classe attuale dell'oggetto fornisca solo "costruttori" con 
side-effect o nel caso in cui, semplicemente, l'oggetto sia "grosso" (il 
tuo metodo implica il crearne una copia, sebbene shallow). Ma anche se 
fosse possibile farlo in tutti i casi mi pare abbastanza complesso.
E infatti non mi era mai venuto in mente e non mi pare una cosa molto 
comune fra gli sviluppatori smalltalk (ed ecco il discorso del fatto che 
un linguaggio influenza il modo di pensare).
Prova a paragonare come estenderesti l'interfaccia a cui risponde un 
oggetto in Smalltalk con come lo faresti in Ruby:
Suppponiamo di avere quanto segue:
class MyClass
   def aMethod
     puts "hallo"
   end
end
my = MyClass.new
Vogliamo aggiungere un metodo all'istanza my e SOLO a quella specifica 
istanza?
def my.anotherMethod
   puts "yup"
end
In pratica è come se si fosse creata una nuova classe valida solo per 
quell'istanza (che continua a rispondere anche alle interfacce a cui 
rispondeva prima, quindi in un certo senso è una classe derivata da 
quella originale).
Se avessi voluto influenzare altre istanze della stessa classe avrei 
dovuto estendere la classe. Il che si può fare tranquillamente:
class MyClass
   def nellaClasse
     puts "ciao"
   end
end
poiché in ruby le classi si possono estendere questa non è una 
ridefinizione ma una estensione.
Ma c'è un altro modo: posso aggiungere un metodo alla classe di un 
oggetto senza sapere quale è (una forma di introspezione, se vuoi, ma 
nota che non ricavo dinamicamente il suo nome):
my.class.module_eval %q{
   def ancheQuesto
     puts "ci sono"
   end
}
E parlando di estendibilità:
require 'dl/import'
module LIBC
   extend DL::Importable
   dlload "libc.so"
   extern "int strlen(const char *)"
end
tre = LIBC.strlen("abc")
Cosa fa su uno unix questo codice dovrebbe essere ovvio.
Nota che dllload e extern sono metodi definiti in 'dl/import' che è una 
libreria esterna: non fanno parte del linguaggio.
Mi fermo perche siamo TERRIBIMENTE off topic.
Il punto iniziale comunque è che il C++, così come QUALUNQUE linguaggio 
(anche Smalltalk, e anche Ruby, e anche Lisp e Scheme etc.) influenza 
MOLTO il modo di pensare.
Quello che conosco meglio è il C++. Di Smalltalk, Lisp, Ruby e altri ho 
solo "scratched the surface" e non sono esperto.
Ritengo C++ un bellissimo linguaggio. Ma molto a basso livello. Quindi 
non per forza il migliore per certi problemi (cosa che IMO vale per 
qualunque linguaggio) e certamente non mi sento di sottoscrivere 
l'affermazione di Nicola che è un linguaggio che non limita il pensiero 
(anche perché ripeto: IMO non esiste un linguaggio che non lo limita).

 tipo in un altro ?
Quella che ho descritto.
Dato che il C++ è "strong typed" dobbiamo limitarci ad un meccanismo che 
  permette di cambiare (apparentemente) il tipo di un oggetto solo 
all'interno di una gerarchia di derivazione.
Abbiamo quindi una classe base che definisce l'interfaccia e le varie 
classi derivate concrete che la implementano più una classe proxy che è 
una classe concreta ... che sembra cambiare tipo:
// nota: scrivo nel news client, codice non testato
class Base {
public:
   // interfaccia pubblica
   // solo metodi virtuali
   // nessun dato
   virtual void esempio()=0;
protected:
   Base():rebind(NULL) {}
   void rebindASAP(Base*b)
   {
     assert(rebind==NULL);
     assert(b->rebind==NULL);
     assert(b!=this);
     rebind=b;
   }
   Base* queryRebind() const
   {
     return rebind;
   }
   void abortRebind()
   {
     assert(rebind!=NULL);
     delete rebind;
     rebind=NULL;
   }
private:
   Base *rebind;
};
class ConcreteA: Base {
   // ...
};
class ConcreteB: Base {
   // supponiamo che il metodo esempio
   // di ConcreteB implichi un cambio
   // di tipo: il nuovo tipo è ConcreteA
   virtual void esempio()
   {
     rebindASAP( new ConcreteA );
   }
   // ...
};
// il proxy
class AConcrete: Base {
   AConcrete()
     :recurse(0)
   {
     // supponiamo che inizialmente
     // il tipo sia ConncreteA
     bindTo( new ConcreteA );
   }
   ~AConcrete()
   {
     assert( current()->queryRebind() == NULL );
     assert( recurse == 0 );
     unbind();
   }
   // implementazione interfaccia
   virtual void esempio()
   {
     pre();
     current()->esempio();
     post();
   }
private:
   unsigned recurse;
   // riutilizzando il meccanismo di rebinding
   // della nostra base per un'altro scopo:
   // tenere traccia del binding attuale
   // nota: le chiamate sono qualificate
   // non per necessità sintattica ma per
   // sottolineare un uso improprio
   Base* current() const { return Base::queryRebind(); }
   void bindTo(Base* b) { Base::rebindASAP(b); }
   void unbind() { Base::abortRebind(); }
   void pre()
   {
     if ( recurse==0 )
       assert( current()->queryRebind() == NULL );
     ++recurse;
   }
   void post()
   {
     assert( recurse!=0 )
     --recurse;
     if ( recurse==0 ) {
       Base* request = current()->queryRebind();
       if ( request != NULL ) {
         unbind();
         bindTo(request);
       }
       assert( current()->queryRebind() == NULL );
     }
   }
};
Mancano alcuni dettagli ma il concetto chiave c'è.

 MOLTO il modo di pensare.
e su questo son d'accordo
-- 
sbi

 e su questo son d'accordo
Vi ricordo che, benche' non ci posti piu' nessuno da quando e' stato
aperto it.comp.lang.python, esiste ancora it.comp.lang che sarebbe il
posto perfetto per queste discussioni.
Ciao,
Nicola

 mi sembra più che comune :)
E' diverso. Molto. Nel mio esempio non hai controllo sulla creazione 
dell'oggetto ne sulla sua classe. Ne su quali altri oggetti ne tengano 
un riferimento o lo utilizzino. Hai un oggetto che ti arriva da qualche 
parte. Punto. Aggiungi un metodo a quell'oggetto (e solo a quello). 
Oppure a tutti gli oggetti di quella classe (ma è più rischioso). Oppure 
(questo capita ed è utile) hai un oggetto e sai che si tratta di una 
classe o di un modulo, ma non sai quale classe sia (puoi scoprirlo, 
grazie alla introspezione, ma non ti interessa scoprirlo). Vuoi 
aggiungere un metodo (o anche dei dati, se è per questo) a quella classe 
(quindi a tutte le sue istanze, anche quelle già create).
Non dico che non si possa fare in Smalltalk. Con compile: e become: e un 
pochino di introspezione probabilmente ci riesci (ma in alcuni casi può 
non essere facile). Ma dico che Ruby supporta queste cose direttamente. 
E il supporto diretto (o nativo) è ben diverso dal "si può".
E la tesi iniziale resta: il linguaggio influenza il modo di pensare (e 
di conseguenza influenza il design). Qualunque linguaggio. Inclusi C++, 
Smalltalk e Ruby.


Nell'ultima risposta ho messo xpost e followup su it.comp.programmare
Effettivamente si potrebbe anche usare it.comp.lang, ma non ci avevo 
pensato.

 a runtime.
Avevo ignorato questo sotto-thread (causa tempo), ma adesso mi sento di 
dire due parole.
Sia Ruby che il C++ non supportano il prodotto matriciale (o 
tensoriale), ma ciò non significa che non siano linguaggi adatti per 
gestire matrici. Si aggiunge semplicemente una libreria. L'importante è 
che si possa ottenere un'integrazione soddisfacente. La programmazione 
OO insegna proprio a pensare in termini di oggetti utente piuttosto che 
di oggetti built-in. Il programmatore ragionerà quindi in termini di 
matrici, non di float o double.
Il C++ è un linguaggio caratterizzato (al 99.9%) da static binding e 
quindi non si può pretendere che supporti nativamente il late binding 
(se ne farebbe probabilmente abuso). La differenza è che mentre il 
programmatore C++ può aggiungere (facilmente) il supporto per il late 
binding, il programmatore Ruby non può aggiungere il supporto per lo 
static binding.
Per realizzare quello che chiedi, è necessario fare quello che si è 
sempre fatto, cioè creare degli oggetti:
Type
Var
Func
Class
e, in particolare, s'implementano Class.add() e Class.del() che 
permettano di aggiungere e rimuovere metodi e variabili.
Ecco risolto il problema. Non abbiamo fatto altro che aggiungere, come 
sempre, astrazioni non supportate nativamente.
Abbiamo così il meglio dei due mondi: late e static binding assieme.
Adesso prova a supportare lo static binding in Ruby.
Kiuhnm
Inviato da X-Privat.Org - Registrazione gratuita 
http://www.x-privat.org/join.php

 Adesso prova a supportare lo static binding in Ruby.
E perché? Dammi un eseempio di design che posso implementare 
direttamente solo con lo static binding.

 direttamente solo con lo static binding.
Altro che design! Ci sono contesti dove, per ragioni d'efficienza, non 
puoi proprio usare Ruby stesso! Che ti piaccia o no, l'efficienza è una 
caratteristica del codice.


 caratteristica del codice.
No. Esiste anche l'efficienza a livello di design. E a livello di 
deployment. E a livello di costi. E sono tutte efficienze che non hanno 
nulla a che vedere con il codice.
L'efficienza complessiva di un progetto non è una grandezza 
unidimensionale. E non dipende solo dal codice.

 unidimensionale. E non dipende solo dal codice.
Chiaro, ma un'alta efficienza complessiva non supplisce all'inefficienza 
