Eu acho que você está afirmando demais quando diz que "fazer novos objetos" sendo impuros implica que quase nada pode ser puro. Se você isolar sua alocação de memória de sua lógica principal, então a lógica da linha principal pode ser feita como "pura" ou "impura" conforme a situação exigir. Uma técnica que uso regularmente é definir funções de manipulação de dados que deixam o objeto this
intacto, mas que usam um parâmetro para um destino no qual os resultados devem ser colocados. (Freqüentemente, eu permito que o destino seja o mesmo que o objeto this
). Por exemplo,
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;
}
};
Estou ciente de que isso não é idiomático C ++ (mais sobre isso em um pouco), mas é uma maneira de implementar funções puras em C ++.
Agora, tentarei explicar alguns recursos estranhos da classe:
Por que usar um método init
, em vez de um construtor?
Eu deliberadamente quis remover quaisquer preocupações de gerenciamento de memória da especificação deste objeto; usar um construtor ou destruidor introduz alguma dependência funcional do comportamento da classe na alocação / liberação de memória.
Como esta função é "pura"?
É "puro" no sentido de que, se sempre dermos uma nova instância do objeto como um parâmetro de destino, os dados de entrada para qualquer operação nunca serão alterados. Por exemplo:
Countdown CountdownArray[10];
CountdownArray[0].init(5);
for(
int Index = 0;
Index < 10 - 1 &&
CountdownArray[Index].decrease(CountdownArray[Index+1];
Index++)
{
continue;
}
Bem, obviamente esse comportamento não é "puro", exatamente, mas os elementos de CountdownArray
não foram modificados desde sua primeira inicialização; o método decrease
faz comportar-se de maneira pura quando recebe um parâmetro de destino apropriado.
Como posso gerenciar a memória dessa classe com eficiência?
Neste caso, a classe é escrita de tal forma que o parâmetro Dest
pode ser o mesmo objeto apontado por this
:
Countdown CD;
CD.init(5);
while(CD.decrase(CD))
{
}
Isso obviamente torna o comportamento menos "puro", mas também torna o código gerado significativamente mais eficiente na memória.
Como esta técnica interage com a herança?
Boa pergunta. Eu evito herança ao usar esta técnica.
Conclusão
Um computador de propósito geral não pode ser praticamente "puro" - temos uma quantidade finita de recursos para trabalhar em um computador (memória, espaço em disco, registradores de CPU) e o uso eficiente desses recursos exige que eles sejam mutáveis. O que as pessoas querem dizer com "pureza" na computação é que isolamos o imutável do mutável, e o estado imutável pode ser considerado "puro". Você está certo de que a alocação de memória pode ter efeitos colaterais em outras partes do sistema (por exemplo, fazendo com que alocações posteriores falhem à medida que ficamos sem memória). Mas pode ser prático calcular a alocação de memória do código que queremos ser puro.