significado significa que programación programacion objetos mutables inmutables inmutable inmutabilidad funcional fisica filosofico clases c++ c++11 functional-programming immutability const-correctness

c++ - que - significado filosofico de inmutable



Forma idiomática de declarar C++ Clases inmutables. (2)

Así que tengo un código funcional bastante extenso donde el tipo de datos principal es estructuras / clases inmutables. La forma en que he estado declarando la inmutabilidad es "prácticamente inmutable" al hacer que las variables miembro y cualquier método consten.

struct RockSolid { const float x; const float y; float MakeHarderConcrete() const { return x + y; } }

¿Es esta realmente la forma en que "deberíamos hacerlo" en C ++? ¿O hay un mejor camino?


La forma en que propuso está perfectamente bien, excepto si en su código necesita realizar la asignación de variables de RockSolid, como esto:

RockSolid a(0,1); RockSolid b(0,1); a = b;

Esto no funcionaría ya que el compilador habría eliminado el operador de asignación de copia.

Por lo tanto, una alternativa es volver a escribir su estructura como una clase con miembros de datos privados, y solo funciones públicas const.

class RockSolid { private: float x; float y; public: RockSolid(float _x, float _y) : x(_x), y(_y) { } float MakeHarderConcrete() const { return x + y; } float getX() const { return x; } float getY() const { return y; } }

De esta manera, sus objetos RockSolid son (pseudo-) inmutables, pero todavía puede hacer asignaciones.


Supongo que su objetivo es la inmutabilidad verdadera: cada objeto, cuando se construye, no puede modificarse. No puedes asignar un objeto sobre otro.

El mayor inconveniente de su diseño es que no es compatible con la semántica de movimientos, lo que puede hacer que las funciones que devuelven tales objetos sean más prácticas.

Como ejemplo:

struct RockSolidLayers { const std::vector<RockSolid> layers; };

podemos crear uno de estos, pero si tenemos una función para crearlo:

RockSolidLayers make_layers();

debe (lógicamente) copiar su contenido al valor de retorno, o usar la sintaxis de return {} para construirlo directamente. Fuera, o tienes que hacer:

RockSolidLayers&& layers = make_layers();

o de nuevo (lógicamente) copiar-construir. La incapacidad de mover-construir obstaculizará varias formas simples de tener un código óptimo.

Ahora, ambas de estas construcciones de copia son elidivas, pero el caso más general es válido: no puede mover sus datos de un objeto con nombre a otro, ya que C ++ no tiene una operación de "destruir y mover" que hace que una variable saque la variable. Alcance y lo utiliza para construir algo más.

Y los casos en los que C ++ moverá implícitamente su objeto ( return local_variable; por ejemplo) antes de la destrucción son bloqueados por sus miembros de datos const .

En un lenguaje diseñado alrededor de datos inmutables, sabría que puede "mover" sus datos a pesar de su inmutabilidad (lógica).

Una forma de solucionar este problema es usar el montón y almacenar sus datos en std::shared_ptr<const Foo> . Ahora la const no está en los datos del miembro, sino en la variable. También puede exponer únicamente las funciones de fábrica para cada uno de sus tipos que devuelva el valor de shared_ptr<const Foo> , bloqueando otras construcciones.

Dichos objetos se pueden componer, con Bar almacenando miembros std::shared_ptr<const Foo> .

Una función que devuelve un std::shared_ptr<const X> puede mover los datos de manera eficiente, y una variable local puede hacer que su estado pase a otra función una vez que haya terminado con ella sin poder meterse con datos "reales".

Para una técnica relacionada, es idomático en C ++ menos restringido tomar ese shared_ptr<const X> y almacenarlos dentro de un tipo de envoltorio que pretenda que no son inmutables. Cuando realiza una operación de mutación, el shared_ptr<const X> se clona y modifica, luego se almacena. Una optimización "sabe" que el shared_ptr<const X> es "realmente" un shared_ptr<X> (nota: asegúrese de que las funciones de fábrica devuelvan un shared_ptr<X> a un shared_ptr<const X> o esto no es realmente cierto), y cuando use_count() es 1, en lugar de eso, desecha const y lo modifica directamente. Esta es una implementación de la técnica conocida como "copia en escritura".