Você está procurando simplificar o gerenciamento e a manutenção da vida de objetos polimórficos no C ++?
Deseja escrever código polimórfico em C ++ tão facilmente quanto em idiomas GC como Java ou C#, sem sacrificar o desempenho?
Você já experimentou outras bibliotecas de programação polimórfica em C ++, mas as achou deficientes?
Nesse caso, esta biblioteca é para você.
"Proxy" é uma biblioteca C ++ moderna que ajuda a usar o polimorfismo (uma maneira de usar diferentes tipos de objetos de forma intercambiável) sem precisar de herança.
O "Proxy" foi criado pelos engenheiros da Microsoft e tem sido usado no sistema operacional Windows desde 2022. Por muitos anos, o uso da herança foi a principal maneira de obter polimorfismo no C ++. No entanto, novas linguagens de programação como Rust oferece melhores maneiras de fazer isso. Melhoramos nossa compreensão da programação orientada a objetos e decidimos usar ponteiros em C ++ como base para "proxy". Especificamente, a biblioteca "proxy" foi projetada para ser:
Consulte as perguntas frequentes do procurador para obter mais antecedentes e consulte as especificações para obter mais detalhes técnicos.
"Proxy" é uma biblioteca C ++ 20 somente para cabeçalho. Para usar a biblioteca, verifique se o seu compilador atende aos requisitos mínimos e inclua o arquivo de cabeçalho proxy.h no seu código -fonte. Como alternativa, você pode instalar a biblioteca via vcpkg ou conan, pesquisando "proxy" (consulte vcpkg.io e conan.io).
Vamos começar com o seguinte exemplo "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"
}Aqui está uma explicação passo a passo:
#include <iostream> : para std::cout .
#include <string> : para std::string .
#include "proxy.h" : para a biblioteca "proxy". A maioria das instalações da biblioteca é definida no namespace pro . Se a biblioteca for consumida via vcpkg ou conan, essa linha deverá ser alterada para #include <proxy/proxy.h> .
struct Streamable : pro::facade_builder ... ::build {} : define um tipo de fachada Streamable . O termo "fachada", formalmente definido como os requisitos do ProFacade , é como a biblioteca "proxy" modela a abstração de tempo de execução. Especificamente,
pro::facade_builder : fornece capacidade para construir um tipo de fachada em tempo de compilação.add_convention : adiciona uma "Convenção de Calling" generalizada, definida por um "Dispatch" e várias "sobrecargas", ao contexto de construção.pro::operator_dispatch <"<<", true> : especifica um despacho para o operador << expressões em que o operando primário ( proxy ) está no lado direito (especificado pelo segundo parâmetro modelo true ). Observe que o polimorfismo na biblioteca "proxy" é definido por expressões, em vez de funções de membros, que são diferentes das funções virtuais C ++ ou de outros idiomas OOP.std::ostream&(std::ostream& out) const : a assinatura da convenção de chamada, semelhante a std::move_only_function . const especifica que o operando primário é const .build : constrói o contexto em um tipo de fachada. pro::proxy <Streamable> p1 = &str : cria um objeto proxy a partir de um ponteiro bruto de std::string . p1 se comporta como um ponteiro cru e não possui a propriedade da std::string subjacente. Se a vida útil do str termina antes de p1 , p1 se tornará pendurado.
std::cout << *p1 : é assim que funciona. Ele imprime "Hello World" porque a convenção de chamada é definida na fachada Streamable , por isso funciona como se ligando para std::cout << str .
pro::proxy <Streamable> p2 = std::make_unique <int>(123) : cria um std::unique_ptr <int> e se converte em um proxy . Diferente de p1 , p2 tem propriedade do int subjacente porque é instanciado de um valor de std::unique_ptr e chama o destruidor de std::unique_ptr quando p2 é destruído, enquanto p1 não tem a propriedade do int subjacente porque é instantiado de um POINTRO cru. p1 e p2 são do mesmo tipo pro::proxy<Streamable> , o que significa que você pode ter uma função que retorna pro::proxy<Streamable> sem expor nenhuma informação sobre os detalhes da implementação ao seu chamador.
std::cout << *p2 : imprime "123" sem surpresa.
pro::proxy <Streamable> p3 = pro::make_proxy <Streamable>(3.14) : cria um proxy a partir de um double sem especificar o tipo de ponteiro subjacente. Especificamente,
p2 , p3 também possui a propriedade do double valor subjacente, mas pode efetivamente evitar a alocação de heap.double ) é conhecido por ser pequeno (nas principais plataformas de 32 ou 64 bits), pro::make_proxy percebe o fato em tempo de compilação e volta ao pro::make_proxy_inplace , o que não garante alocação de heap.std::function e de outros invólucros polimórficos existentes no padrão. std::cout << *p3 : impressões "3.14" sem surpresa.
Quando main retornos, p2 e p3 destruirão os objetos subjacentes, enquanto p1 não faz nada porque mantém um ponteiro bruto que não possui a propriedade do std::string subjacente.
Além das expressões do operador demonstradas no exemplo anterior, a biblioteca suporta quase todas as formas de expressões em C ++ e pode torná -las polimórficas. Especificamente,
PRO_DEF_MEM_DISPATCH : define um tipo de despacho para expressões de chamada de função de membro.PRO_DEF_FREE_DISPATCH : define um tipo de despacho para expressões de chamada de função gratuita.pro::operator_dispatch : tipo de despacho para expressões de operador.pro::conversion_dispatch : tipo de despacho para expressões de conversão.Observe que algumas instalações são fornecidas como macro, porque os modelos C ++ hoje não suportam a geração de uma função com um nome arbitrário. Aqui está outro exemplo que faz com que a função do membro chama expressões 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"
}Aqui está uma explicação passo a passo:
#include <iostream> : para std::cout .#include <sstream> : para std::stringstream .#include "proxy.h" : para a biblioteca "proxy".PRO_DEF_MEM_DISPATCH (MemDraw, Draw) : define um tipo de despacho MemDraw para expressões de chamada de função de membro da Draw .PRO_DEF_MEM_DISPATCH (MemArea, Area) : define um tipo de despacho MemArea para expressões da Area de função de membro de chamada.struct Drawable : pro::facade_builder ... ::build {} : define um tipo de fachada Drawable . Especificamente,add_convention : adiciona convenções de chamada ao contexto de construção.support_copy < pro::constraint_level ::nontrivial> : Especifica que o tipo de ponteiro subjacente deve ser copável, o que também torna o tipo de proxy resultante copável.class Rectangle : uma implementação do Drawable .PrintDrawableToString : converte um Drawable em uma std::string . Observe que esta é uma função e não um modelo de função, o que significa que pode gerar ABI em um sistema de construção maior.pro::proxy<Drawable> p = pro::make_proxy<Drawable, Rectangle>(3, 5) : cria um objeto proxy<Drawable> contendo um Rectangle .std::string str = PrintDrawableToString(p) : converte p em um std::string , cria implicitamente uma cópia de p .std::cout << str : imprime a string.A biblioteca "proxy" é uma solução independente para o polimorfismo de tempo de execução no C ++. Existem muitos outros recursos documentados nas especificações. Além dos recursos mencionados acima, aqui está uma lista com curadoria dos recursos mais populares com base no feedback do usuário:
facade_builder::add_convention é mais poderoso do que o demonstrado acima. Pode levar qualquer número de tipos de sobrecarga (formalmente, qualquer tipo que atenda aos requisitos de prooverload ) e executar a resolução de sobrecarga padrão ao invocar um proxy .facade_builder::add_facade permite composição flexível de diferentes abstrações.PRO_DEF_WEAK_DISPATCH de um tipo de despacho existente e uma implementação padrão.allocate_proxy é capaz de criar um proxy a partir de um valor com qualquer alocador personalizado. Em C ++ 11, std::function e std::packaged_task tinham construtores que aceitavam alocadores personalizados para ajuste de desempenho, mas estes foram removidos em C ++ 17 porque "a semântica não é clara e há problemas técnicos com o armazenamento de um alocador em um contexto e depois recuperando esse alocador posteriormente para qualquer aloca necessária durante a cópia". Esses problemas não se aplicam ao allocate_proxy .facade_builder fornece suporte completo para a configuração de restrições, incluindo layout de memória (por restrict_layout ), cópia (por support_copy ), relocatabilidade (por support_relocation ) e destrutibilidade (por support_destruction ).proxy suporta reflexão no tempo de compilação baseada em tipo para consultas de tempo de execução. Consulte o facade_builder::add_reflection and Function Model proxy_reflect para obter mais detalhes. | Família | Versão mínima | Sinalizadores necessários |
|---|---|---|
| GCC | 13.1 | -std = c ++ 20 |
| Clang | 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 projeto recebe contribuições e sugestões. A maioria das contribuições exige que você concorde com um Contrato de Licença de Colaborador (CLA) declarando que você tem o direito e, na verdade, concede -nos os direitos de usar sua contribuição. Para detalhes, visite https://cla.opensource.microsoft.com.
Quando você envia uma solicitação de tração, um BOT do CLA determina automaticamente se você precisa fornecer um CLA e decorar o PR adequadamente (por exemplo, verificação de status, comentar). Simplesmente siga as instruções fornecidas pelo bot. Você só precisará fazer isso uma vez em todos os repositórios usando nosso CLA.
Este projeto adotou o Código de Conduta Open Microsoft. Para obter mais informações, consulte o Código de Conduta Perguntas frequentes ou entre em contato com [email protected] com quaisquer perguntas ou comentários adicionais.
Este projeto pode conter marcas comerciais ou logotipos para projetos, produtos ou serviços. O uso autorizado de marcas comerciais ou logotipos da Microsoft está sujeito e deve seguir as diretrizes de marca registrada e marca da Microsoft. O uso de marcas comerciais da Microsoft ou logotipos em versões modificadas deste projeto não deve causar confusão ou implicar o patrocínio da Microsoft. Qualquer uso de marcas comerciais ou logotipos de terceiros estão sujeitas às políticas de terceiros.