¿Todos los objetos en C ++ son mutables si no se indica lo contrario?

8

¿Todos los objetos en C ++ son mutables si no se indica lo contrario?

En Python y Javascript no puedo cambiar cadenas, tuplas, unicodes. Me preguntaba si hay algo en C ++ que sea inmutable o si cada objeto es mutable y tengo que usar el constcalificador de tipo para hacerlo inmutable.

GM
fuente
2
¿Qué quieres decir con "por defecto"?
B 10овић
Que todos los objetos en C ++ son mutables si no se indican de manera diferente.
GM
1
Sí lo son. Tenga en cuenta que esto no es lo mismo que la palabra clave "mutable".
Olav
3
La pregunta en el cuerpo de la pregunta es diferente de la pregunta en el título.
user253751
@immibis solo fue para agregar un poco de concurso
GM

Respuestas:

18

La inmutabilidad ha sido bien entendida por algún tiempo. Python, Java y C ++ tienen diferentes modelos de memoria que dificultan las comparaciones directas. El autor del artículo que citó originalmente no parece conocer C ++.

Al igual que en Python, Java y la mayoría de los lenguajes de paradigmas múltiples, C y C ++ permiten la mutabilidad de forma predeterminada. Esto es lo que los programadores suelen querer: si tengo una String xvariable, quiero poder asignar un nuevo valor x = "foo".

El sistema const en C y C ++ permite una gran cantidad de inmutabilidad matizada que falta en Python, Java e incluso Scala. Si una función C ++ toma a const std::string&o a const char*, promete (y el compilador se asegura, hasta cierto punto), que esta función no cambiará (¡no puede!) El contenido de esa cadena. Dado un objeto const, solo podemos invocar métodos de ese objeto que también están marcados como const. Si una clase C ++ solo tiene miembros públicos const, entonces el objeto es efectivamente inmutable.

Sin embargo, esto a veces es confuso ya que en C y C ++ los objetos son ubicaciones de memoria y las variables son nombres para ubicaciones de memoria. Por el contrario, las variables en Python y Java son nombres para punteros a objetos. En un lenguaje con semántica de referencia, x = ysignifica "hacer que x apunte al mismo objeto que y". Como solo estamos copiando punteros, esto es posible con objetos inmutables. En un lenguaje con semántica de valor como C ++, significa "actualizar el contenido de xcon el contenido de y". Por lo tanto, si se desea la reasignación de una variable en C o C ++, la variable puede no tener un tipo constante. Para hacer esto con objetos inmutables, tendríamos que usar un puntero explícitamente.

Que Java y Python utilicen objetos de cadena inmutables es una decisión de diseño fundamental, pero no está directamente relacionado con los beneficios de la inmutabilidad en un entorno de subprocesos múltiples. Una razón es que los literales de cadena en el código fuente se pueden agrupar, lo que reduce la cantidad de objetos. Esto también es posible en C / C ++. En C ++, el literal "foo"tiene tipo const char[4](el cuarto carácter es la terminación '\0'). Otra razón es que las entradas en conjuntos y claves en dictos / mapas no deben cambiarse. Dado que las cadenas se usan de forma generalizada como claves dict (la mayoría de los objetos de Python son un dict), la inmutabilidad elimina una fuente común de errores. En Java, otra razón para las cadenas inmutables es el modelo de seguridad de Java. Todas estas razones no tienen ninguna relación con el subprocesamiento múltiple.

Si Java se construyera con la inmutabilidad en mente, el lenguaje habría tenido un aspecto muy diferente. Si bien está inspirado en C ++, los diseñadores se esforzaron por crear un lenguaje mucho más simple, deshacerse de la constante es uno de esos pasos. Lo equivalente de Java a una referencia constante de C ++ es un adaptador o decorador que implementa cualquier método de mutación como throws new NotImplementedException(), y reenvía las llamadas de método no mutante a la colección real. El hecho de que todas las interfaces de la colección java.util impliquen mutabilidad es una clara señal de que no se esforzaron por un lenguaje de inmutabilidad primero.

La solución que Java presentó para resolver los problemas de concurrencia no era la inmutabilidad, sino el bloqueo generalizado. Cada objeto contiene un mutex que puede usarse para synchronizedbloques o métodos completos. Como resultado, eso no es bueno para el rendimiento, no escala muy bien y es bastante propenso a errores: aún debe lidiar con un estado global mutable.

amon
fuente
13
Esto es lo que los programadores generalmente quieren => esa es una afirmación bastante controvertida. Mi experiencia es en realidad lo opuesto: la mayoría de las variables locales son inmutables, y la variable en ocasiones mutable. Los lenguajes como Rust (donde la mutabilidad requiere una mutpalabra clave y el compilador señala una mutabilidad innecesaria) tienden a respaldar mi hipótesis: en Rust, tienes muchos más letenlaces que los que tienes let mut.
Matthieu M.
1
@MatthieuM .: Eso se debe en parte a for x in 1..10que no se utiliza let mutpara definir la variable del bucle, pero obviamente requiere algo de mutabilidad (solo a nivel del bucle, no dentro del cuerpo del bucle).
MSalters
@MSalters: Por supuesto, pero C ++ tiene gama-para la sintaxis también :)
Matthieu M.
44
@MSalters Yo caracterizaría eso como unir 10 variables llamadas x con 10 valores, no mutar uno
Caleth
1
@MatthieuM. Estoy de acuerdo en que la inmutabilidad es buena, pero en C ++ simplemente no siempre es una opción, o al menos excesivamente incómoda. Por ejemplo, inicializar una variable constante en varios pasos requiere que lo hagamos en una función separada donde ese objeto aún no sea constante. Al menos C ++ 11 nos permite usar lambdas invocadas de inmediato como const T o = [&]{T o; o.init(...); return o;}();... yay para API que no fueron diseñadas teniendo en cuenta la inmutabilidad.
amon
9

Casi. El lenguaje en sí trata todo como mutable a menos que lo use const, pero aún es posible construir objetos inmutables sin decirle al compilador que son inmutables, simplemente no proporcionando una forma de mutarlos.

Por ejemplo, toma esta clase:

class MyClass {
    int value;
public:

    MyClass(int v) : value(v) {}
    int getValue() {return value;}

    MyClass &operator=(const MyClass &other) = delete;
};

Las instancias de MyClassson inmutables, porque no hay forma de mutarlas, pero en ninguna parte del código se dice que son inmutables. (Esta es la misma forma en que las instancias de la Stringclase de Java son inmutables)

usuario253751
fuente
(Me estoy oxidando) ¿El hecho deletede que el operador de asignación de copia aquí también asegure que uno no puede asignar desde una referencia de valor mediante la operación de asignación de movimiento?
underscore_d
¿Y destruir el objeto con una llamada explícita al destructor y luego volver a crearlo con la nueva ubicación cuenta como "mutante"?
Peter Green
1
@underscore_d Cuenta como usuario que define el operador con el propósito de no agregar implícitamente una asignación de movimiento, sí
Caleth
Me pregunto si un mecanismo para que el compilador sepa que los objetos de cierta clase siempre son inmutables podría conducir a una mejor optimización. Por ejemplo, sería innecesario actualizar los objetos cobrados. En este ejemplo en particular (y generalmente en clases que siguen este patrón) probablemente ayudaría declarar valueconst y declarar getValue()una función const.
Peter - Restablece a Mónica el
1
@PeterGreen No, eso cuenta como destruir el objeto y crear uno nuevo.
user253751
2

No, hay una excepción: Lambdas y, por extensión, los objetos que capturan son inmutables por defecto:

int i = 0;

[i]{//capturing the value i
    i++; //does not compile, i is const
};

[i]() mutable{ //make the lambda mutable
    i++; //compiles fine
};

Entonces, en lugar de agregar constpara hacerlo inmutable, agrega mutablepara que no lo sea const.

nwp
fuente