es posible lograr el modelo de propiedad de Rust con un contenedor genérico de C ++?

15

Mirando este artículo sobre la seguridad de concurrencia de Rust:

http://blog.rust-lang.org/2015/04/10/Fearless-Concurrency.html

Me preguntaba cuántas de estas ideas se pueden lograr en C ++ 11 (o más reciente). En particular, ¿puedo crear una clase de propietario que transfiera la propiedad a cualquier método al que se pueda pasar? Parece que C ++ tiene tantas formas de pasar variables que sería imposible, pero ¿tal vez podría poner algunas restricciones en la clase o plantilla para asegurar que se ejecute algún código de plantilla con cada paso de método?

Brannon
fuente
Algunas citas del enlace mejorarían esta pregunta
Martin Ba
2
@delnan (Safe) Rust garantiza que nunca tendrá más de una referencia mutable a algo a la vez y que nunca tendrá una referencia mutable a algo a lo que también tenga referencias de solo lectura. También tiene algunas restricciones para transferir datos entre subprocesos. Juntos, evitan una clase significativa de errores relacionados con subprocesos y facilitan el razonamiento sobre el estado de los objetos, incluso en código de subproceso único.
CodesInChaos
3
No cree que pueda expresar los préstamos de una manera que el compilador de C ++ pueda verificar, por lo que tendría que recurrir a la ejecución en tiempo de ejecución con el impacto de rendimiento asociado.
CodesInChaos
1
¿No están implementados los propietarios de alcance por punteros inteligentes en C ++ 11?
Akshat Mahajan
1
@JerryJeremiah Rust tiene una amplia variedad de tipos de referencia. Los básicos, &no requieren ningún tipo de promoción para ser utilizados. Si intenta obtener un &muttiempo, todavía tiene otra referencia (mutable o no) al mismo elemento, no podrá compilar. RefCell<T>mueve el cheque al tiempo de ejecución, por lo que obtendrá pánico si intenta .borrow_mut()algo que ya tiene un activo .borrow()o .borrow_mut(). Rust también tiene Rc<T>(puntero de propiedad compartido) y su hermano Weak<T>, pero se trata de propiedad, no de mutabilidad. Pegue un RefCell<T>dentro de ellos para la mutabilidad.
8bittree

Respuestas:

8

C ++ tiene tres formas de pasar parámetros a una función: por valor, por referencia de valor y por referencia de valor. De estos, pasar por valor crea propiedad en el sentido de que la función llamada recibe su propia copia, y pasar por rvalue reference indica que el valor puede ser consumido, es decir, el llamador ya no lo usará. Pasar por referencia de valor significa que el objeto se toma prestado temporalmente de la persona que llama.

Sin embargo, estos tienden a ser "por convención" y no siempre pueden ser verificados por el compilador. Y puede convertir accidentalmente una referencia de valor en una referencia de valor utilizando std::move(). Concretamente, hay tres problemas:

  • Una referencia puede sobrevivir al objeto al que hace referencia. El sistema de por vida de Rust evita esto.

  • Puede haber más de una referencia mutable / no constante activa en cualquier momento. El comprobador de préstamos de Rust lo impide.

  • No puede optar por no recibir referencias. No puede ver en un sitio de llamadas si esa función crea una referencia a su objeto, sin conocer la firma de la función llamada. Por lo tanto, no puede evitar de manera confiable las referencias, ni eliminando ningún método especial de sus clases ni auditando el sitio de la llamada para el cumplimiento de alguna guía de estilo de "no referencias".

El problema de por vida es sobre la seguridad básica de la memoria. Por supuesto, es ilegal usar una referencia cuando el objeto referenciado ha expirado. Pero es muy fácil olvidarse de la vida útil cuando almacena una referencia dentro de un objeto, en particular cuando ese objeto sobrevive al alcance actual. El sistema de tipo C ++ no puede explicar esto porque no modela la vida útil de los objetos.

El std::weak_ptrpuntero inteligente codifica la semántica de propiedad de forma similar a una referencia simple, pero requiere que el objeto referenciado se gestione mediante un shared_ptr, es decir, se cuente por referencia. Esta no es una abstracción de costo cero.

Si bien C ++ tiene un sistema constante, esto no rastrea si un objeto puede modificarse, sino que rastrea si un objeto puede modificarse a través de esa referencia en particular . Eso no proporciona suficientes garantías para la "concurrencia intrépida". Por el contrario, Rust garantiza que si hay una referencia mutable activa que es la única referencia ("Soy el único que puede cambiar este objeto") y si hay referencias no mutables, entonces todas las referencias al objeto no son mutables ("Aunque puedo leer del objeto, nadie puede cambiarlo").

En C ++, puede sentirse tentado a proteger el acceso a un objeto a través de un puntero inteligente con un mutex. Pero como se discutió anteriormente una vez que tenemos una referencia, puede escapar de su vida útil esperada. Por lo tanto, dicho puntero inteligente no puede garantizar que sea el único punto de acceso a su objeto administrado. Tal esquema en realidad puede funcionar en la práctica porque la mayoría de los programadores no quieren sabotearse a sí mismos, pero desde el punto de vista del sistema de tipos esto todavía es completamente incorrecto.

El problema general con los punteros inteligentes es que son bibliotecas además del lenguaje central. El conjunto de características principales del lenguaje permite estos punteros inteligentes, por ejemplo, std::unique_ptrnecesita constructores de movimiento. Pero no pueden solucionar las deficiencias dentro del lenguaje central. Las habilidades para crear referencias implícitamente cuando se llama a una función y para tener referencias colgantes juntas significan que el lenguaje central de C ++ no es sólido. La incapacidad de limitar las referencias mutables a una sola significa que C ++ no puede garantizar la seguridad contra las condiciones de carrera con ningún tipo de concurrencia.

Por supuesto, en muchos aspectos, C ++ y Rust son más parecidos de lo que son diferentes, en particular con respecto a sus conceptos de vidas de objetos determinadas estáticamente. Pero si bien es posible escribir programas C ++ correctos (siempre que ninguno de los programadores cometa errores), Rust garantiza la corrección con respecto a las propiedades discutidas.

amon
fuente
Si el problema es que C ++ no rastrea la propiedad en el lenguaje central, ¿sería posible implementar esa funcionalidad a través de la metaprogramación? Lo que significa que crearía una nueva clase de puntero inteligente que sería segura para la memoria (1) forzándola a apuntar exclusivamente a objetos que solo usan punteros inteligentes de la misma clase y (2) rastreando la propiedad a través de plantillas
Elliot Gorokhovsky
2
@ElliotGorokhovsky No, porque la plantilla no puede deshabilitar las funciones del lenguaje principal, como las referencias. Un puntero inteligente puede dificultar la obtención de una referencia, pero en ese momento estás luchando contra el lenguaje: la mayoría de las funciones estándar de la biblioteca necesitan referencias. Tampoco es posible verificar la duración de una referencia a través de plantillas porque el lenguaje no ofrece un concepto reified de duración.
amon
Ya veo, gracias
Elliot Gorokhovsky