Newsgroup: it.comp.lang.c++
Titolo: int Const::Const();

Dato che i costruttori non possono restituire informazioni in caso di 
fallimento (almeno non in modo naturale), ho pensato di usare 
costruttori vuoti e di fare tutto in funzioni init. Premetto che non 
posso usare la gestione strutturata delle eccezioni (try...catch).
Per non complicare le cose con la restituzione degli error code, faccio 
ereditare a ogni classe pubblica una semplice struttura che forse in 
futuro modificherò:
struct ErrorInfo
{
   unsigned error;
};
Ogni classe definisce i propri error code tramite semplici enum.
Esiste un approccio migliore, pur restando nel semplice?
Kiuhnm

 Esiste un approccio migliore, pur restando nel semplice?
Lo scopo concettuale di un costruttore e' di impostare gli invarianti di 
classe. Se i costruttori non fanno niente, vuol dire che non ci sono 
invarianti, e allora tanto vale avere tutto public, cioe' una struct.
Se invece ci sono degli invarianti di classe, l'inizializzazione puo' 
fallire, non si vuole usare la gestione delle eccezioni del C++,
e si vuole allocare gli oggetti nello heap (con new), consiglio di usare 
un metodo statico di creazione di oggetti.
Per esempio:
class C {
public:
    static C *CreaC() {
       C* p = new C;
       if (fallito) return 0;
       else return p;
    }
private:
    C() { }
    int x; // Invariante: x e' compreso tra 0 e 9
};
-- 
     Carlo Milanesi
 
http://digilander.libero.it/carlmila

 Kiuhnm

 };
Sinceramente non mi piace molto. Voglio che la classe C possa essere 
allocata anche sullo stack. I puntatori interni puntano ad oggetti di 
dimensione sconosciuta e alcuni di essi possono essere manipolati anche 
in seguito. Sarebbe troppo scomodo ed inefficiente dover ogni volta 
distruggere e ricreare l'oggetto.
Non potrei creare un costruttore "scalabile" che cerchi di fare quanto 
gli viene richiesto e in caso di fallimento restituisca l'oggetto in uno 
stato ben definito?
Per es. il costruttore può essere
C(Filter f) { select(f); }
select fallisce silenziosamente e l'errore può essere controllato dal 
chiamante. Gli operatori new e delete saranno miei.
L'utente dovrebbe poter fare questo:
C c(GAUSSIAN16);
if (c.error)
{
   c.select(GAUSSIAN4);
   if (c.error)
     return;
}
Nel mio sistema ci saranno situazioni dove la memoria può essere poca e 
quindi è importante poter fare questo genere di cose.
Kiuhnm

 possano essere definiti virtual (e quindi manco lasciati incompleti)
Non riesco a pensare a un metodo che non comporti lo stravolgimento 
della classe.
Al limite puoi fare segnalare l'errore dal linker:
class C
{
   C();
   C(int) {}
   void prova()
   {
     C c;
   }
public:
   static C* create()
   {
     return new C(6);
   };
   void testAll()
   {
     prova();
   }
};
int main()
{
   int i = 0;
   if (i)
   {
     C* c = C::create();
     c->testAll();
   }
}
Kiuhnm

 wrote in message 
news:446b6bde$0$14784$4faf...@reader4.news.tin.it...

O emetti una eccezione o imposti uno stato interno dell'oggetto:
http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.2
Quindi se non puoi emettere eccezioni...imposti lo stato interno
Ciao
Fabio

 Quindi se non puoi emettere eccezioni...imposti lo stato interno
Veramente penso si possano escogitare migliaia di metodi.
Per es:
#include <iostream>
using namespace std;
struct C
{
     C(int* all_ok = 0)
     {
         if (all_ok)
             *all_ok = 0;
     }
};
#define TRY(x)		all_ok = 0;		\
                     x(&all_ok);		\
                     if (!all_ok)	\
                         return -1;	\
int all_ok;
int main()
{
     TRY(C c);
     TRY(C d);
     cout << "Tutto OK" << endl;
}
Kiuhnm

 Veramente penso si possano escogitare migliaia di metodi.
Che č una variante dello stato, solo che invece di essere interna allo
istanza č esterna e restituita al chiamante. Onestamente non vedo
alternative reali a eccezione o stato. Se ti secca tanto al limite imposta
la variabile globale errno, e poi la testi alla fine dell'inizializzazione
(sempre se non hai dipendenze tra gli oggetti). Oppure lancia un segnale da
qualche parte.

 qualche parte.
Anche le eccezioni sono una variante dello stato, quindi si sta appunto 
cercando di capire quale di esse può risultare più comoda.
Una possibilità è quella di emulare le eccezioni, ma non credo ne valga 
la pena. Il "metodo del return" che ho usato è piuttosto potente come 
concetto perché impone che ogni funzione sia exception safe.
Kiuhnm


Pero' e' un po' innaturale, il che va benissimo fin tanto che il codice
lo manipoli solo tu. In termini di manutenibilita' richiede che chi
arriva impari una convenzione del tutto nuova.
Alla fine mi pare comunque preferibile l'uso dell'inizializzazione in
due fasi: costruttore che tutt'al piu' inizializza i membri dati
scalari e una bella funzione init() che svolge il ruolo del costruttore
e ritorna un codice di errore.
Ciao,
Nicola

 concetto perché impone che ogni funzione sia exception safe.
Se non vuoi le eccezioni questa diventa una specifica non una feature.
Personalmente adotto tutte e due le tecniche a seconda dei tempi di
sviluppo che mi sono concessi. Le funzioni tornano un codice di errore
univoco per errore, e poi ho un funzione globale che in base all'errore mi
lancia l'eccezione giusta. Qualcosa del genere.
namespace Err
{
	enum err_code
	{
		no_error                   = 0,
		//bla bla
	}
	void GeneraEccezione(int e);
}
void Err::GeneraEccezione(int e)
{
	if(Err::no_error == e)
		return;
	if(e >= Err::DB_start_error && e <= Err::DB_end_error)
		throw Err::ErrDB(e);
	if(e >= Err::BZS_start_error && e <= Err::BZS_end_error)
		throw Err::ErrBZip(e);
	throw Err::ErrBase(e);
}
poi di solito uso le funzioni cosě.
Err::err_code e = Istanza.Funzione(..)
if( e != Err::err_code )
	//bla,bla
Ma se devo fare il codice in fretta il tutto diventa
GeneraEccezione( Istanza.Funzione(..) )
e di fatto NON gestisco l'eccezione.
Codice non bello, ma negli anni non ho mai sentito l'esigenza di cambiarlo.

 e ritorna un codice di errore.
Infatti alla fine ho scelto questa, anche se il codice d'errore 
preferisco sia interno alla classe. In questo modo posso eseguire varie 
funzioni e poi vedere se c'è stato un errore.
A volte può essere utile.
I 4 byte in più per classe non sono un problema. Le classi più piccole 
non hanno problemi d'inizializzazione (almeno nel mio progetto).
Kiuhnm

 A volte può essere utile.
No, adesso che ricordo, il motivo è un altro. Voglio che gli eventuali 
valori restituiti dalle funzioni non abbiano niente a che fare con la 
segnalazione degli errori. Questo evita che ogni funzione abbia un suo 
modo di restituire gli errori (a seconda di cosa restituisca).
Kiuhnm

 Se non vuoi le eccezioni questa diventa una specifica non una feature.
E' una feature invece.
Secondo te perché, nell'STL, Pop() non restituisce l'elemento che 
elimina? Un motivo riguarda l'efficienza, mentre l'altro l'exception 
safety. Scrivere funzioni exception safe è *difficile*.
Se puoi, dai un'occhiata all'item 10 di "Exceptional C++".
Raccomando a tutti di studiarsi gli item dall'8 al 17. Il metodo del 
return richiederebbe di scrivere funzioni a prova di proiettili. Secondo 
te non è una feature? Fossero tutte scritte così...
Il trucco (e la difficoltà) sta nel rinunciare alla catch().
Kiuhnm

 Secondo te non è una feature?
Guarda, per è solo un problema di incomprensione con il mio italiano. Se tu
"DEVI" scrivere funzioni exception safe allora io la chiamo "specifica".
Poi se nessuna funzione lancia direttamente, o indirettamente attraverso il
run time una eccezione, io definisco il progetto formalmente "exception
safe". Ovvero definisco un progetto scritto interamente in C "exception
safe" per partito preso. Ma la mia è solo una questione terminologica oltre
il pittoresco.
Poi, nel mio mondo non mi sono mai preoccupato tanto di essere "exception
safe", ma più che altro "thread safe". Nel mio caso, se c'è un errore
irreversibile, se la funzione non è a prova di proiettile, la cosa più
saggia è che scriva qualcosa nel log e che il processo termini. Il tempo di
sviluppo per scrivere codice ottimale di solito non ce l'ho, e mi
accontento di codice buono, e mediocre nel caso di sviluppo di demo. 

 il pittoresco.
Una funzione si dice Exception Safe se, in presenza di eccezioni, non 
provoca una perdita (leak) di risorse.
Si dice inoltre Strongly Exception Safe se, qualora la funzione fallisca 
per un'eccezione, sarà come se tale funzione non fosse mai stata chiamata.
Kiuhnm

 Raccomando a tutti di studiarsi gli item dall'8 al 17.
Non e' vero che in generale e' molto difficile scrivere codice 
applicativo exception safe.
Cio' che e' difficile e' garantire l'exception safety quando si scrive 
un template di classe o un template di funzione in cui uno o piu' 
parametri del template sono classi che possono sollevare eccezioni.
Infatti tutti gli esempi di exception safety contenuti nel citato libro 
di Sutter sono casi di template di classi in cui non si fanno ipotesi su 
quali possano essere le classi che verranno usate per istanziare tali 
template.


 }
Un possibile invariante e' il seguente: "error" e' diverso da zero e il 
resto dell'oggetto e' indefinito, oppure "error" e' zero e il resto 
dell'oggetto ha dei valori validi.
Tale invariante e' sensato, ma ha il difetto che costringe a verificare 
la variabile "error" tutte le volte che si accede all'oggetto.


 provoca una perdita (leak) di risorse.
Allora stai attento tu che usi Visual Studio. Storicamente (sospetto) che
le loro librerie tendono a perdere memoria allegramente, o per essere
precisi se faccio una fografia della memoria come prima riga del main, e ne
faccio una come ultima riga del main, e ovviamente nel mezzo faccio
qualcosa tipo aprire un file e leggerlo, i conti non mi sono mai tornati. 

 provoca una perdita (leak) di risorse.
Piu' esattamente, il programma raggiunge uno stato valido, anche se
magari a priori non e' noto quale.
Ciao,
Nicola

 ha scritto nel messaggio 
news:e1dv7fwtogta.1xh39w2sld9lo$.dlg@40tude.net...


int main()
{
    logMemoryMetrics();
    A a;
    logMemoryMetrics();
   // a esce di scope qui
}
e dirai parlavo di heap
class A{
  HugeB* b_;
  A(const A& other);
  A& operator=(const A& other);
 public:
  A(): b_( new HugeB() ) {}
  virtual ~A(){ delete b_; }
};
direi che ci sarebbero mille altri modi per "soverchiare" quel modo di 
trovare un memory leak.
Un sospetto un po' piů concreto di leak sarebbe dato dal vedere "crescere la 
memoria utilizzata"
a runtime senza motivo piuttosto che avere un overhead che potrebbe essere 
costante.
-- 
sbi

 la variabile "error" tutte le volte che si accede all'oggetto.
Qui si parla di eccezioni, non di errori di programmazione. E' diverso.
Dopo aver chiamato una funzione che non può fallire, non controlli 
l'invariante, se non temi in un errore di programmazione.
Kiuhnm

 qualcosa tipo aprire un file e leggerlo, i conti non mi sono mai tornati. 
Nell'ultimo compilatore sono state aggiunte ottimizzazioni, corretti 
errori, ecc... quindi non credo che restino errori tanto gravi nella 
versione 8, comunque terrò un occhio aperto.
Kiuhnm

 magari a priori non e' noto quale.
Qual è la definizione di stato valido?
Kiuhnm


Dipende dal contesto, non esiste una definizione assoluta.
Ciao,
Nicola

 parametri del template sono classi che possono sollevare eccezioni.
E quando scrivi una classe che si appoggia ad altre classi, non hai la 
stessa situazione? I template inseriscono un'indirezione sintattica, ma 
non aggiungono un problema. Quando usi un'altra classe, non puoi dirti 
sicuro soltanto perché sai esattamente come si chiama. Per te, che si 
chiami T o si chiami Register, non ha molta differenza. Entrambe 
potrebbero generare eccezioni.
Kiuhnm

 Dipende dal contesto, non esiste una definizione assoluta.
In teoria la tua definizione è quella corretta, ma in pratica non aiuta 
molto proprio per la sua genericità. In quella che ho dato andrebbe 
aggiunto un aggettivo per far capire che non è la definizione generale 
di exception safety, però è la più comune.
Kiuhnm

 trovare un memory leak.
Parlavo ovviamente di qualcosa del genere:
int main()
{
    //creo e inizializzo i miei eventuali oggetti globali
    //fotografia 1
    //chiamata a funzione contenente il "vero" programma
    //fotografia 2, e ...
   // .. i conti non tornano
}
Intanto tutto il runtime del VC++ usa l'accoppiata malloc/free (piaccia o
non piaccia), quindi con quel metodo non risolvi un gran che. Devi
taroccare la malloc/free, oppure (molto meglio) comprare un Bound Checker
equivalente (se esiste ancora) e turarti il naso. Per la cronaca comunque
parlo delle funzioni non standard del tipo _CrtSetDbgFlag,
_CrtSetReportMode, _CrtMemCheckpoint disponibili con il compilatore
Microsoft. 
Devo dire, e qui devo dare ragione alla tua chiosa finale, ovvero che
usando il run time del c/c++ di casa Microsoft non ho mai notato una
crescita costante della memoria occupata con il tempo, ma è più un
overhead. Ma la cosa comunque mi da fastidio. Un poco come i socket sotto
Windows che si allocano di default un buffer di qualche Kb, e non lo
mollano "subito" alla chiusura del socket stesso, e non lo riutilizzano se
ne apri uno appena dopo. Sotto Linux c'è un controllo migliore delle
risorse associate ai socket. Questo per fare un esempio border line. 
Se programma in Python me ne frego se la mia applicazione occupa 50 Mb,
basta che faccia quel che deve fare. Se programmo in C/C++ mi piace avere
tutti i byte sotto il mio controllo.


Scusa, ma la mia definizione e' del tutto analoga a definire il
concetto di programma corretto; un programma e' corretto se fa quel che
deve fare, vero o no? Pero' se non entri nel merito di cosa il
programma deve effettivamente fare la definizione e' vera quanto
inutile.
Lo stesso vale per la sicurezza "debole" dalle eccezioni: ha
un'utilita' pratica solo se calata in contesti specifici. Resta pero'
la definizione corretta; la tua non e' solo sbagliata, ma e' nociva,
perche' crea l'illusione di poter risolvere il problema senza curarsi a
fondo della semantica del programma e della sua salvaguardia.
Ciao,
Nicola

 fondo della semantica del programma e della sua salvaguardia.
Se vogliamo proprio essere precisi un programma è corretto se dato un 
modello, lo segue alla lettera. Il predicato "il programma P è conforme 
al modello M" è indecidibile quindi non si può *mai* parlare di 
programma corretto. Diciamo che un programma è corretto se supera tutti 
i nostri test (sintetici, reali, ecc...) e, come vedi, abbiamo già 
deviato dalla teoria.
Un programma si dice exception safe se funziona correttamente anche in 
presenza di eccezioni.
Cosa ci facciamo con questa definizione? La stessa cosa che ci facciamo 
con la definizione di programma corretto.
Per questo motivo, nell'ambito del C++, Dave Abrahams decise di definire 
due "garanzie" di exception safety: una base (objects don't leak 
resources) e una forte (complete "rollback"). In gergo, si è quindi 
soliti parlare di oggetti exception safe e strongly exception safe 
facendo riferimento a queste garanzie. Non ci vedo niente di male.
Kiuhnm


Non sono d'accordo.
Se la classe e' un parametro di template, non hai nessuna idea di quali 
sue funzioni possano sollevare eccezioni e di quali eccezioni possa 
sollevare.
Se la classe non e' un parametro di template, allora quando scrivi la 
classe che la usa devi averne a disposizione l'interfaccia, e 
nell'interfaccia si possono usare le specifiche di eccezione, che ti 
fanno sapere quali funzioni possono sollevare quali eccezioni.
Se poi hai a disposizione tutto il codice sorgente delle classi che usi, 
puoi determinare dall'implementazione di tali classi quali eccezioni 
possono sollevare.


 l'invariante, se non temi in un errore di programmazione.
Non ho detto che si deve controllare l'invariante, ma che si deve 
controllare la variabile "error" tutte le volte che il resto 
dell'oggetto potrebbe avere uno stato indefinito. Per esempio:
   a.leggi();
   if (! a.error) a.mostra();
   if (! a.error) b = a;


   if (! a.error) b = a;
Non è necessario controllare passo passo, in generale.
E comunque ritengo che restituire valori ed errori sia una pratica 
scomoda in grandi progetti. Penso solo a quante volte ho dovuto leggere 
come ogni singola funzione segnalasse l'errore.
Kiuhnm

 possono sollevare.
Il programmatore non è un bambino che deve ogni volta essere bacchettato 
dal compilatore. Si sta cercando di introdurre i "concepts" in 
particolare per i template, ma il programmatore è comunque sempre libero 
di specificare un suo protocollo.
Quando scrivi un driver per un sistema operativo il compilatore non ti 
segnala i mille errori che commetti eppure devi attenerti a molte regole 
decise dagli sviluppatori dell'OS.
Kiuhnm


Questo è legittimo e non significa perdere memoria.
Perdere memoria significa che ci sono sequenze che continuano ad 
allocare sempre più memoria senza mai liberarla anche se dal punto di 
vista logico le risorse vengono rilasciate.
Se tu fai
footprint_memoria
apri_file
chiudi_file
footprint_memoria
e noti delle differenze non è un problema.
se invece fai
footprint_memoria
for i da 1 a N
   apri_file
   chiudi_file
end for
footprint_memoria
e la differenza cresce monotonicamente con il crescere di N, allora stai 
perdendo memoria.

 programma corretto.
Il predicato è indecidibile in generale. Contestualizzando può invece
essere dimostrata la correttezza rispetto alle specifiche di un dato
programma.
Per esempio una volta scritta la semantica formale dei costrutti in
gioco puoi scrivere un programma che analizza un altro programma.
Tipicamente questo è molto costoso, alla fine si riescono a fare
dimostrazioni formali solo su piccoli pezzi di programma, ma per ragioni
di costo (e ovviamente dipende dalle specifiche).
Spesso poi si sceglie di usare l'interpretazione astratta (o per lo meno
noi facciamo così). Perdiamo precisione, ma spesso ci arriviamo in
fondo. Perdere precisione non vuole dire "dire cose sbagliate", ma
"accettare di non poter dire nulla".[0]
Spesso il risultato che possiamo dare è "Don't know" (equivalente alla
non terminazione della macchina di turing che analizzasse la correttezza
della macchina di turing rappresentante il programma oggetto). Ma si
riesce anche a dare un Correct o un Fails. 
-- 
blog:  
http://www.akropolix.net/rik0/blogs
 | Uccidete i filosofi,
site:  
http://www.akropolix.net/rik0/
      | tenetevi riso e
forum: 
http://www.akropolix.net/forum/
     | bacchette per voi.

 riesce anche a dare un Correct o un Fails. 
Il problema è comunque che il programma andrà implementato con un 
linguaggio che produce codice che si appoggia a un sistema operativo che 
a sua volta utilizza driver, ecc...
E' veramente difficile garantire che un programma funzionerà bene.
A volte è necessario modificare il proprio codice sebbene l'errore sia 
nella documentazione, nel compilatore, nella libreria usata o 
addirittura nel sistema operativo.
Il predicato è indecidibile anche in pratica, se consideri l'intero sistema.
Kiuhnm

 Il predicato è indecidibile anche in pratica, se consideri l'intero sistema.
Dipende da quanto è grosso il programma che analizzi. Per esempio
analizzare la correttezza di un algoritmo numerico può essere banale (a
seconda dell'algoritmo chiaramente, uno che -- per dire -- usasse al suo
interno un teorema tipo l'ultimo di fermat chiederebbe alla macchina 
1, di dimostrare il teorema di fermat
2. di avere "in memoria" come fatto tale teorema
).
In generale però è probabile che problemi di overflow siano trattabili.
Io di persona sto lavorando al problema della string cleaness (non che
il fatto che lo faccia io sia garanzia di correttezza, eh... solo per
parlarne).
In pratica la mia tesi (quando trovo il tempo di iniziarla) sarà
scrivere appunto un programma che dati frammenti di codice C è in grado
di dire se al loro interno gli accessi alle stringhe sono ok, oppure se
c'è qualcosa che non va (o che potrebbe non andare).
La teoria che c'è sotto è già stata in parte sviluppata, e nel prototipo
sembrava funzionare (così per lo meno dicono quelli che lo hanno fatto).
La teoria effettivamente funziona.
Si tratta di decorare il codice C di partenza riconducendo stringhe e
puntatori a numeri, e poi fare dei calcoli su questi numeri, per
controllare che tutto funzioni.
E già questo, pur non garantendo la correttezza di un programma (possono
esistere errori di altro tipo) può comunque essere un passo verso tale
scopo.
Peraltro come sai senza dubbio meglio di me i compilatori ottimizzanti
si basano in pratica sugli stessi principi. Ovvero sul dimostrare che
due frammenti di codice fanno la stessa cosa e scegliere il "più
veloce". E in effetti spesso ci sono bachi da cinema e nelle librerie
numeriche dicono, non usare questa ottimizzazione etc etc etc.


 numeriche dicono, non usare questa ottimizzazione etc etc etc.
Buona fortuna per il tuo progetto. Ciò che rende veramente difficile 
questo genere di cose è il dover affrontare problemi NP-hard ad ogni passo.
Kiuhnm

 questo genere di cose è il dover affrontare problemi NP-hard ad ogni passo.
Io non lavoro nel campo dell'ottimizzazione eh :)
Io mi limito al problema di "correttezza" di cui sopra. 
Per lavorare ad ottimizzare un compilatore ci vuole ben altra esperienza
(e poi non saprei bene cosa potrei fare di utile in una tesi di laurea
triennale).


 Io mi limito al problema di "correttezza" di cui sopra. 
Immagino che farai un certo tipo di assunzioni allora. Per es. immagina 
un codice del genere:
// codice corretto
....
if (GetSolution(NP-complete_problem) == TRUE)
{
   // Codice scorretto
}
Questo programma è corretto?
E il seguente?
// codice corretto
....
while (...)
{
   d = GetRandData();
   if (check_conjecture(d) == FALSE)
   {
     // Codice scorretto
   }
}
E potrei andare avanti con esempi molto più complessi...
Kiuhnm

 E potrei andare avanti con esempi molto più complessi...
Non ce ne è bisogno di esempi più complessi. Tipicamente daremmo in
entrambi i casi un "don't know" dove c'è il "codice scorretto".
D'altra parte spesso anche con un input generico daremmo un don't know.
Io invece posso essere facilitato, per certi versi.
write(0, *p. 10)
non è un don't know, ma mi da tutte le informazioni di cui ho bisogno
per "marcare" le variabili fittizie associate al puntatore/stringa
(ammesso che ci sia spazio abbastanza, ma in generale dovrei saperlo, se
non ci sono state operazioni insicure prima).
Invece per me risulta letale una gets (nel senso che tutto quello che
riguarda quella striga diventa un don't know, e probabilmente quello che
segue e il resto del programma).


 facendo riferimento a queste garanzie. Non ci vedo niente di male.
Ciao,
Nicola

 vista logico le risorse vengono rilasciate.
Hai perfettamente ragione. Il mio problema è che ho mai "perso" tempo a
fare dei testi sulla libreria di run-time del VC++, e di natura non sono
eccessivamente fiducioso.
Di solito, anzi sempre, se c'è una perdita di memoria visibile è dovuto ad
un mio errore di codice (MFC a parte che non uso da tempo immemore).

 scritto c'e' una bella differenza.
Sì, lo davo per scontato. Mea culpa.
Kiuhnm