Penso che stai affermando troppo quando dici che "far diventare impuri nuovi oggetti" implica che quasi nulla può essere puro. Se si isola l'allocazione di memoria dalla logica principale, la logica della linea principale può essere resa "pura" o "impura" come richiesto dalla situazione. Una tecnica che uso regolarmente consiste nel definire le funzioni di manipolazione dei dati che lasciano intatto l'oggetto this
, ma che prendono un parametro per una destinazione in cui devono essere posizionati i risultati. (Spesso, permetto alla destinazione di essere uguale all'oggetto this
.) Ad esempio,
class Countdown
{
private:
int _CountLeft;
public:
void init(int CountLeft) { this->_CountLeft = CountLeft; }
int getCountLeft() const { return this->_CountLeft; }
bool decrease(Countdown &Dest) const {
Dest._CountLeft = this->_CountLeft - 1;
return Dest._CountLeft > 0;
}
};
Sono consapevole che questo non è un C ++ idiomatico (ne parlerò di più in un po '), ma è un modo per implementare pure funzioni in C ++.
Ora, proverò a spiegare alcune caratteristiche strane della classe:
Perché utilizzare un metodo init
, invece di un costruttore?
Volutamente volevo rimuovere qualsiasi problema di gestione della memoria dalle specifiche di questo oggetto; l'uso di un costruttore o di un distruttore introduce alcune dipendenze funzionali del comportamento della classe sull'allocazione / rilascio della memoria.
In che modo questa funzione è "pura"?
È "puro" nel senso che, se diamo sempre una nuova istanza dell'oggetto come parametro di destinazione, i dati di input a qualsiasi operazione non cambiano mai. Ad esempio:
Countdown CountdownArray[10];
CountdownArray[0].init(5);
for(
int Index = 0;
Index < 10 - 1 &&
CountdownArray[Index].decrease(CountdownArray[Index+1];
Index++)
{
continue;
}
Bene, ovviamente questo comportamento non è "puro", esattamente, ma gli elementi di CountdownArray
non sono stati modificati dalla loro prima inizializzazione; il metodo decrease
fa si comporta in modo puro, quando viene fornito un parametro di destinazione appropriato.
Come posso gestire efficacemente la memoria per questa classe?
In questo caso, la classe è scritta in modo che il parametro Dest
possa essere lo stesso oggetto puntato da this
:
Countdown CD;
CD.init(5);
while(CD.decrase(CD))
{
}
Questo ovviamente rende il comportamento meno "puro", ma rende anche il codice generato significativamente più efficiente in termini di memoria.
In che modo questa tecnica interagisce con l'ereditarietà?
Buona domanda. Evito l'ereditarietà quando utilizzo questa tecnica.
Conclusione
Un computer per uso generico non può essere praticamente "puro": abbiamo una quantità limitata di risorse con cui lavorare in un computer (memoria, spazio su disco, registri della CPU) e l'uso efficiente di queste risorse richiede che siano mutabili. Ciò che la gente intende per "purezza" nell'informatica è che abbiamo isolato l'immutabile dal mutevole, e lo stato immutabile può essere considerato "puro". Hai ragione che l'allocazione della memoria può avere effetti collaterali su altre parti del sistema (ad esempio causando il fallimento delle allocazioni successive quando esauriamo la memoria). Ma può essere pratico calcolare l'allocazione di memoria dal codice che vogliamo essere puri.