Allocazione e purezza della memoria manuale

5

Linguaggio come Haskell ha un concetto di purezza . In pura funzione, non posso mutare alcuno stato a livello globale. In ogni caso Haskell estrae completamente la gestione della memoria, quindi l'allocazione della memoria non è un problema qui.

Ma se le lingue possono gestire la memoria direttamente come C ++, per me è molto ambiguo. In questi linguaggi, l'allocazione della memoria rende visibile la mutazione. Ma se tratto il fare un nuovo oggetto come un'azione impura, in realtà, quasi nulla può essere puro. Quindi il concetto di purezza diventa quasi inutile.

Come devo gestire la purezza nelle lingue che hanno memoria come oggetto globale visibile?

    
posta Eonil 09.04.2012 - 05:32
fonte

4 risposte

0

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.

    
risposta data 09.04.2012 - 06:50
fonte
1

Non sono d'accordo con le premesse di base.

C ++ (per usare il tuo esempio, sebbene lo stesso si applichi a C, Pascal, Ada, ecc.) non dà alcuna reale visibilità nell'heap. È possibile tentare di allocare memoria, che potrebbe avere esito positivo o negativo, ma non si ha visibilità sul motivo per cui un'allocazione ha avuto esito positivo o negativo, né quali altre allocazioni potrebbero aver portato a tale successo / fallimento.

In altre parole, allocare memoria in un unico posto non ha un effetto che è direttamente visibile da qualsiasi altra parte. Sì, è possibile che un'assegnazione possa portare al fallimento di un'altra, ma 1) notare che il C ++ portatile può metterle insieme, e 2) di solito è quasi impossibile realizzare quella connessione anche in un modo non portatile.

Nella direzione opposta, nulla in Haskell (o in qualsiasi altra lingua) può modificare i fondamenti coinvolti in ogni caso. Il tentativo di allocare più memoria di quella disponibile (anche come spazio degli indirizzi virtuali) avrà esito negativo, indipendentemente dalla lingua. Se l'utente esegue un altro programma che si occupa di tutta la memoria, né C ++ né Haskell (o molto altro) possono fare molto al riguardo, quindi un'allocazione che avrebbe avuto successo in una sessione potrebbe non riuscire in un'altra.

In tutta onestà, suppongo che dovrei aggiungere che anche se non è portabile, molti (la maggior parte?) gestori di heap includono funzioni extra per percorrere l'heap corrente, vedere quali blocchi sono allocati, ecc. Sì, suppongo che una cosa del genere potrebbe essere visto come una rottura di "purezza", ma la mia ipotesi è che le persone che includono il camminare nel mucchio dei loro software probabilmente non sono molto infastidite da quello (e quelli che si preoccupano veramente della purezza non usano quella capacità.

    
risposta data 09.04.2012 - 06:24
fonte
1

La purezza è un concetto che non diventa inutile solo perché il linguaggio di programmazione non lo impone. Haskell potrebbe essere la soluzione migliore se cerchi un'assoluta garanzia di purezza, ma ciò non significa che il concetto sia inutile in C ++. Puoi sicuramente aumentare la purezza del codice in C ++, e questo può certamente avere un effetto positivo sulla sua qualità.

    
risposta data 09.05.2012 - 08:52
fonte
0

C ++ ha la capacità di fornire tali costrutti / moduli.

potresti implementare questi livelli di astrazione, se lo desideri.

Per far rispettare ciò, potresti iniziare aggiungendo const a tutto, quindi utilizzare i costruttori degli oggetti per definire il loro stato, quindi introdurre i puntatori intelligenti, quindi probabilmente rendersi conto che è lento e creare gli allocatori locali dei thread e così via ...

Apparentemente, GHC produrrà anche programmi in C - forse questo output potrebbe aiutare a visualizzare come può essere affrontato usando C o C ++?

    
risposta data 09.05.2012 - 10:14
fonte

Leggi altre domande sui tag