¿Está buscando simplificar la gestión y el mantenimiento de la vida útil de los objetos polimórficos en C ++?
¿Quieres escribir código polimórfico en C ++ tan fácilmente como en idiomas GC como Java o C#, sin sacrificar el rendimiento?
¿Has probado otras bibliotecas de programación polimórfica en C ++ pero las has encontrado deficientes?
Si es así, esta biblioteca es para usted.
"Proxy" es una biblioteca moderna de C ++ que lo ayuda a usar el polimorfismo (una forma de usar diferentes tipos de objetos indistintamente) sin necesidad de herencia.
El "proxy" fue creado por los ingenieros de Microsoft y se ha utilizado en el sistema operativo Windows desde 2022. Durante muchos años, usar la herencia fue la forma principal de lograr el polimorfismo en C ++. Sin embargo, los nuevos lenguajes de programación como Rust ofrecen mejores formas de hacer esto. Hemos mejorado nuestra comprensión de la programación orientada a objetos y decidimos usar punteros en C ++ como base para el "proxy". Específicamente, la biblioteca "proxy" está diseñada para ser:
Consulte las preguntas frecuentes del poder para obtener más antecedentes y consulte las especificaciones para obtener más detalles técnicos.
"Proxy" es una biblioteca C ++ 20 de solo encabezado. Para usar la biblioteca, asegúrese de que su compilador cumpla con los requisitos mínimos e solo incluya el archivo de encabezado proxy.h en su código fuente. Alternativamente, puede instalar la biblioteca a través de VCPKG o Conan, buscando "proxy" (ver vcpkg.io y conan.io).
Comencemos con el siguiente ejemplo de "Hello World":
# include < iostream >
# include < string >
# include " proxy.h "
struct Streamable : pro::facade_builder
::add_convention<pro::operator_dispatch< " << " , true >, std::ostream&(std::ostream& out) const >
::build {};
int main () {
std::string str = " Hello World " ;
pro::proxy<Streamable> p1 = &str;
std::cout << " p1 = " << *p1 << " n " ; // Prints: "p1 = Hello World"
pro::proxy<Streamable> p2 = std::make_unique< int >( 123 );
std::cout << " p2 = " << *p2 << " n " ; // Prints: "p2 = 123"
pro::proxy<Streamable> p3 = pro::make_proxy<Streamable>( 3.14 );
std::cout << " p3 = " << *p3 << " n " ; // Prints: "p3 = 3.14"
}Aquí hay una explicación paso a paso:
#include <iostream> : para std::cout .
#include <string> : para std::string .
#include "proxy.h" : para la biblioteca "proxy". La mayoría de las instalaciones de la biblioteca se definen en el espacio de nombres pro . Si la biblioteca se consume a través de VCPKG o Conan, esta línea debe cambiarse a #include <proxy/proxy.h> .
struct Streamable : pro::facade_builder ... ::build {} : define un tipo de fachada Streamable . El término "fachada", definido formalmente como los requisitos de Profacade , es cómo la biblioteca "proxy" modela la abstracción de tiempo de ejecución. Específicamente,
pro::facade_builder : proporciona capacidad para construir un tipo de fachada en el tiempo de compilación.add_convention : agrega una "convención de llamadas" generalizada, definida por un "despacho" y varias "sobrecargas", al contexto de compilación.pro::operator_dispatch <"<<", true> : especifica un despacho para operador << expresiones donde el operando primario ( proxy ) está en el lado derecho (especificado por el segundo parámetro de plantilla true ). Tenga en cuenta que el polimorfismo en la biblioteca "proxy" se define por expresiones en lugar de funciones miembros, que es diferente de las funciones virtuales de C ++ u otros lenguajes OOP.std::ostream&(std::ostream& out) const : la firma de la convención de llamadas, similar con std::move_only_function . const especifica que el operando primario es const .build : Construye el contexto en un tipo de fachada. pro::proxy <Streamable> p1 = &str : Crea un objeto proxy a partir de un puntero bruto de std::string . p1 se comporta como un puntero crudo, y no tiene la propiedad de la std::string subyacente. Si la vida de str termina antes de p1 , p1 se vuelve colgando.
std::cout << *p1 : Así es como funciona. Imprime "hola mundo" porque la convención de llamadas se define en la fachada Streamable , por lo que funciona como si llamara std::cout << str .
pro::proxy <Streamable> p2 = std::make_unique <int>(123) : crea un std::unique_ptr <int> y se convierte en un proxy . A diferencia de p1 , p2 tiene la propiedad del int subyacente porque se instancia a partir de un valor de std::unique_ptr , y llamará al destructor de std::unique_ptr cuando p2 se destruye, mientras que p1 no tiene la propiedad del int subyacente porque está instanciado desde un puntero bruto. p1 y p2 son del mismo tipo pro::proxy<Streamable> , lo que significa que puede tener una función que devuelve pro::proxy<Streamable> sin exponer ninguna información sobre los detalles de implementación a su persona que llama.
std::cout << *p2 : Imprime "123" sin sorpresa.
pro::proxy <Streamable> p3 = pro::make_proxy <Streamable>(3.14) : Crea un proxy a partir de un double sin especificar el tipo de puntero subyacente. Específicamente,
p2 , p3 también tiene la propiedad del valor double subyacente, pero puede evitar efectivamente la asignación de almacenamiento intermedio.double ) es pequeño (en las principales plataformas de 32 o 64 bits), pro::make_proxy realiza el hecho en el tiempo de compilación y se vuelve a ser pro::make_proxy_inplace , que no garantiza una asignación de montón.std::function y otros envoltorios polimórficos existentes en el estándar. std::cout << *p3 : Imprime "3.14" sin sorpresa.
Cuando main regresan, p2 y p3 destruirán los objetos subyacentes, mientras que p1 no hace nada porque contiene un puntero bruto que no tiene propiedad de la std::string ::.
Además de las expresiones del operador demostradas en el ejemplo anterior, la biblioteca admite casi todas las formas de expresiones en C ++ y puede hacerlas polimórficas. Específicamente,
PRO_DEF_MEM_DISPATCH : define un tipo de envío para las expresiones de llamadas de función de miembro.PRO_DEF_FREE_DISPATCH : define un tipo de envío para expresiones de llamada de función gratuita.pro::operator_dispatch : Tipo de despacho para expresiones de operador.pro::conversion_dispatch : Tipo de despacho para expresiones de conversión.Tenga en cuenta que algunas instalaciones se proporcionan como macro, porque las plantillas de C ++ hoy en día no admiten generar una función con un nombre arbitrario. Aquí hay otro ejemplo que hace que las expresiones de llamadas de la función miembro sean polimórficas:
# include < iostream >
# include < sstream >
# include " proxy.h "
PRO_DEF_MEM_DISPATCH (MemDraw, Draw);
PRO_DEF_MEM_DISPATCH (MemArea, Area);
struct Drawable : pro::facade_builder
::add_convention<MemDraw, void (std::ostream& output)>
::add_convention<MemArea, double () noexcept >
::support_copy<pro::constraint_level::nontrivial>
::build {};
class Rectangle {
public:
Rectangle ( double width, double height) : width_(width), height_(height) {}
Rectangle ( const Rectangle&) = default ;
void Draw (std::ostream& out) const {
out << " {Rectangle: width = " << width_ << " , height = " << height_ << " } " ;
}
double Area () const noexcept { return width_ * height_; }
private:
double width_;
double height_;
};
std::string PrintDrawableToString (pro::proxy<Drawable> p) {
std::stringstream result;
result << " entity = " ;
p-> Draw (result);
result << " , area = " << p-> Area ();
return std::move (result). str ();
}
int main () {
pro::proxy<Drawable> p = pro::make_proxy<Drawable, Rectangle>( 3 , 5 );
std::string str = PrintDrawableToString (p);
std::cout << str << " n " ; // Prints: "entity = {Rectangle: width = 3, height = 5}, area = 15"
}Aquí hay una explicación paso a paso:
#include <iostream> : para std::cout .#include <sstream> : para std::stringstream .#include "proxy.h" : para la biblioteca "proxy".PRO_DEF_MEM_DISPATCH (MemDraw, Draw) : Define un tipo de despacho MemDraw para expresiones de la función de llamadas Función Draw .PRO_DEF_MEM_DISPATCH (MemArea, Area) : define un tipo de envío MemArea para expresiones de llamadas Area de función de miembro.struct Drawable : pro::facade_builder ... ::build {} : define un tipo de fachada Drawable . Específicamente,add_convention : agrega convenciones de llamadas al contexto de compilación.support_copy < pro::constraint_level ::nontrivial> : Especifica el tipo de puntero subyacente será copiado, lo que también hace que el tipo proxy resultante sea copiado.class Rectangle : una implementación de Drawable .PrintDrawableToString : Convierte un Drawable en una std::string . Tenga en cuenta que esta es una función en lugar de una plantilla de función, lo que significa que puede generar ABI en un sistema de compilación más grande.pro::proxy<Drawable> p = pro::make_proxy<Drawable, Rectangle>(3, 5) : crea un objeto proxy<Drawable> que contiene un Rectangle .std::string str = PrintDrawableToString(p) : convierte p en una std::string , crea implícitamente una copia de p .std::cout << str : imprime la cadena.La biblioteca "proxy" es una solución autónoma para el polimorfismo de tiempo de ejecución en C ++. Hay muchas otras capacidades documentadas en las especificaciones. Además de las características mencionadas anteriormente, aquí hay una lista curada de las características más populares basadas en los comentarios de los usuarios:
facade_builder::add_convention es más poderoso de lo demostrado anteriormente. Puede tomar cualquier cantidad de tipos de sobrecarga (formalmente, cualquier tipo que cumpla con los requisitos de provergamiento ) y realizar una resolución de sobrecarga estándar al invocar un proxy .facade_builder::add_facade permite una composición flexible de diferentes abstracciones.PRO_DEF_WEAK_DISPATCH desde un tipo de envío existente y una implementación predeterminada.allocate_proxy puede crear un proxy a partir de un valor con cualquier asignador personalizado. En C ++ 11, std::function and std::packaged_task tenía constructores que aceptaron asignadores personalizados para el ajuste de rendimiento, pero estos se eliminaron en C ++ 17 porque "las semánticas no tienen claras las no claras, y existen problemas técnicos para almacenar un asignador en un contexto erguido y luego recuperar ese alocador más tarde para cualquier aloción necesaria durante la copeta de la tarea". Estos problemas no se aplican a allocate_proxy .facade_builder proporciona soporte completo para la configuración de restricciones, incluido el diseño de memoria (por restrict_layout ), Copyability (by support_copy ), Retocatability (por support_relocation ) y destructibilidad (por support_destruction ).proxy admite la reflexión de tiempo de compilación basado en el tipo para consultas de tiempo de ejecución. Consulte facade_builder::add_reflection y plantilla de función proxy_reflect para obtener más detalles. | Familia | Versión mínima | Banderas requeridas |
|---|---|---|
| GCC | 13.1 | -std = c ++ 20 |
| Sonido metálico | 15.0.0 | -std = c ++ 20 |
| MSVC | 19.31 | /STD: C ++ 20 |
| Nvidia HPC | 24.1 | -std = c ++ 20 |
git clone https://github.com/microsoft/proxy.git
cd proxy
cmake -B build
cmake --build build -j
ctest --test-dir build -j
Este proyecto da la bienvenida a las contribuciones y sugerencias. La mayoría de las contribuciones requieren que acepte un Acuerdo de Licencia de Contributor (CLA) que declare que tiene derecho y realmente hacernos los derechos para utilizar su contribución. Para más detalles, visite https://cla.opensource.microsoft.com.
Cuando envíe una solicitud de extracción, un BOT CLA determinará automáticamente si necesita proporcionar un CLA y decorar el PR adecuadamente (por ejemplo, verificación de estado, comentario). Simplemente siga las instrucciones proporcionadas por el bot. Solo necesitará hacer esto una vez en todos los reposos usando nuestro CLA.
Este proyecto ha adoptado el Código de Conducta Open Open Microsoft. Para obtener más información, consulte el Código de Conducta Preguntas frecuentes o comuníquese con [email protected] con cualquier pregunta o comentario adicional.
Este proyecto puede contener marcas comerciales o logotipos para proyectos, productos o servicios. El uso autorizado de marcas o logotipos de Microsoft está sujeto y debe seguir las pautas de marca y marca de Microsoft. El uso de marcas registradas de Microsoft o logotipos en versiones modificadas de este proyecto no debe causar confusión o implicar el patrocinio de Microsoft. Cualquier uso de marcas comerciales o logotipos de terceros está sujeto a las políticas de esas partes de terceros.