Newsgroup: it.comp.lang.c++
Titolo: Domanda (sintassi dei costruttori)

> 
http://www.geocities.com/sunsetstrip/stage/7787
 <<
<<<<<<<<<<<< _
Metal...@iol.it
 >>>>>>>>>>>>>>
(remove antispamming asterisks for mailbox reply)

NULL), giusto?
Giusto: se il ctor e` chiamato, implicitamente o esplicitamente, senza
argomenti, il compilatore fa conto che sia stato chiamato con un solo
argomento pari a NULL -- in questo uso, detto degli "argomenti di
default", un ctor non si distingue per nulla da qualsiasi altra funzione
(sia membro ovvero globale).
Ma torniamo alla lista di ctor inizializzanti.  Il segno inconfondibile
del
principiante C++ e` l'uso di idiomi del tipo...:
class unaclasse {
//...
public:
    unaclasse();
    unaclasse(const char*, long);
//...
};
class altraclasse {
    unaclasse x;
public:
    altraclasse();
//...
};
altraclasse::altraclasse()
{
    x = unaclasse("paralipomeni",1234);
}
Questo idioma significa: l'oggetto x viene inizializzato
col suo costruttore di default; poi, quasi immediatamente,
tutto questo lavoro di inizializzazione viene buttato via
per sostituire x, attraverso un'operatore di assegnazione,
con un altro valore di tipo unaclasse.  Questo spreco di
lavoro e` assurdo.  Il programmatore sensato scrive:
altraclasse::altraclasse():
    x("paralipomeni", 1234)
{
}
cioe`, specifica direttamente i giusti argomenti al ctor
dei membri-dati (qui, x) dell'oggetto di tipo altraclasse.
Questo, per ricordare che la sintassi della lista di ctor
inizializzanti si applica ai membri-dati non meno che
alle basi!
E` buona prassi abituarsi a usare ctor inizializzanti per
TUTTI i membri dati della classe che richiedano una
qualsiasi inizializzazione non di default, piuttosto che
non operatori di assegnazione nel corpo del ctor.  Se
si tratta di dati di tipi built-in la cosa sara`, al peggio,
equivalente; in generale, ci saranno vantaggi su tutti
i piani (efficienza, chiarezza, correttezza), senza
nessuno svantaggio.
E` inoltre buona prassi, nella lista dei ctor inizializzanti,
usare lo stesso ordine in cui i ctor stessi vengono poi
comunque eseguiti:
    -- tutte le eventuali basi virtuali,
    -- poi tutte le ev. basi non virtuali da sinistra a destra,
    -- poi tutti gli ev. membri dati nell'ordine in cui sono
        dichiarati nella classe
Questo, appunto, e` l'ordine di esecuzione dei ctor, ed
e` molto piu` chiaro se l'ordine in cui li scriviamo e` lo
stesso in cui verranno eseguiti (molti compilatori danno
un warning se i due ordini differiscono, purtroppo il VC++
non e` fra questi).
Alex

 }
E se gli argomenti del costruttore non sono noti ma sono il risultato di
operazioni complesse?
Ti faccio un esempio:
CAnObject::CAnObject (CStream &inStream)
{
	long i;
	CAnObjectDef* def =
			dynamic_cast<CAnObjectDef*>(CSerializable::loadFrom (inStream));
	_ON_NULL_(def);
	mDef = (*def);
	delete def;
	...
}
Non č il massimo della pulizia, ma ho dovuto fare cosě per come č articolata
la gestione della persistenza tramite la classe CSerializable.
La funzione loadFrom () crea l'istanza in base al tipo specificato T nello
stream attraverso un identificatore univoco che tutte le sottoclassi di
CSerializable registrano, poi chiama il costruttore T(CStream&) e restituisce
un CSerializable*. Di conseguenza, una eventuale chiamata del costruttore
CAnObjectDef (inStream) prima del corpo della funzione non terrebbe in
considerazione l'identificatore della classe presente nello stream. Per
correggere questo problema dovrei quanto meno saltare la lettura
dell'identificatore della classe. Ma questa č sempre una operazione da
inserire nel corpo del costruttore di CAnObject.
Ciao

inserire nel corpo del costruttore di CAnObject.
Come vedi, si puo` benissimo correggere senza saltare la lettura
in questione, e non e` vero che sia *sempre* una funzione da inserire
*nel corpo* del costruttore (puo` benissimo, volendo, andare nel
ctor-inizializzatore, grazie all'uso di una funzione, magari inline).  Se
ne valga la pena, come dicevo, e` un altro discorso.
Se mai, al contrario, a fronte di inizializzazioni cosi` complesse e
soprattutto "a rischio" (di errori runtime vari), bisogna vedere se non
sia il caso di imitare un tipico idioma che Microsoft usa moltissimo
nelle MFC (e anche in ATL...) per varie ragioni: "costruzione in due
tempi" -- un costruttore "peso-piuma", che fa proprio il minimo di
lavoro per mettere un oggetto in uno stato stabile e noto (ma non
necessariamente di alcuna utilita`); una funzione-membro che fa il
vero lavoro (tipicamente Create in MFC, FinalCostruct in ATL).  Ci
sono vantaggi e svantaggi in questo tipo di scelte, naturalmente.
Alex

 ne valga la pena, come dicevo, e` un altro discorso.
Ho pensato una soluzione con la definizione di un costruttore apposito da
selezionare tramite un parametro fittizio.
CAnObject::CAnObject (CStream& inStream):
	mDef (inStream, ctorAct_ReadClassID)
{
	...
}
Il costruttore può essere inserito automaticamente nella classe CAnObjectDef
con facilità con una define o meglio con un template
CAnObjectDef::CAnObjectDef (CStream& inStream, TctorAction inAction)
{
	CSerializable::loadID (inStream, GetRuntimeClassID ());
	// lancia una exception se l'identificatore non e' quello passato
	loadFrom (inStream);
}
Le funzioni che seguono vanno riscritte per tutte le classi serializabili.
TRuntimeClassID CAnObjectDef::GetRuntimeClassID (void)
{
	return ....
}
void CAnObjectDef::loadFrom (CStream& inStream)
{
	...
}
Questa soluzione, che può essere utilizzata se l'oggetto letto dallo stream
appartiene alla classe CAnObjectDef, è più efficiente. Viceversa,  non
funziona se prevedo che l'oggetto letto possa appartenere ad una delle
sottoclassi di CAnObjectDef.
Ciao
-- 
Fabrizio Di Giuseppe
fabrizio.KILLME.@iride.com
(remove ".KILLME." from the address to send me a message)

non operatori di assegnazione nel corpo del ctor.
grazie per la spiegazione molto approfondita (come al solito  ;-)))


grazie per la spiegazione molto approfondita (come al solito  ;-)))
Prego!
Alex