Cherchez-vous à simplifier la gestion et la maintenance des objets polymorphes en C ++?
Voulez-vous écrire du code polymorphe en C ++ aussi facilement que dans les langages GC comme Java ou C #, sans sacrifier les performances?
Avez-vous essayé d'autres bibliothèques de programmation polymorphe en C ++ mais les avez-vous trouvées déficientes?
Si c'est le cas, cette bibliothèque est pour vous.
"Proxy" est une bibliothèque C ++ moderne qui vous aide à utiliser le polymorphisme (un moyen d'utiliser différents types d'objets de manière interchangeable) sans avoir besoin d'héritage.
"Proxy" a été créé par Microsoft Engineers et est utilisé dans le système d'exploitation Windows depuis 2022. Pendant de nombreuses années, l'utilisation de l'héritage était le principal moyen d'atteindre le polymorphisme en C ++. Cependant, de nouveaux langages de programmation comme la rouille offrent de meilleures façons de le faire. Nous avons amélioré notre compréhension de la programmation orientée objet et avons décidé d'utiliser des pointeurs en C ++ comme fondement du "proxy". Plus précisément, la bibliothèque "Proxy" est conçue pour être:
Veuillez vous référer aux questions fréquemment posées par le proxy pour plus de contexte et référez-vous aux spécifications pour plus de détails techniques.
"Proxy" est une bibliothèque C ++ 20 en tête uniquement. Pour utiliser la bibliothèque, assurez-vous que votre compilateur répond aux exigences minimales et incluez simplement le fichier d'en-tête proxy.h dans votre code source. Alternativement, vous pouvez installer la bibliothèque via VCPKG ou CONAN, en recherchant "Proxy" (voir VCPKG.IO et CONAN.IO).
Commençons par l'exemple suivant "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"
}Voici une explication étape par étape:
#include <iostream> : pour std::cout .
#include <string> : pour std::string .
#include "proxy.h" : pour la bibliothèque "proxy". La plupart des installations de la bibliothèque sont définies dans Namespace pro . Si la bibliothèque est consommée via VCPKG ou CONAN, cette ligne doit être changée en #include <proxy/proxy.h> .
struct Streamable : pro::facade_builder ... ::build {} : définit un type de façade Streamable . Le terme "façade", officiellement défini comme les exigences de Profacade , est de savoir comment la bibliothèque "proxy" modèles d'abstraction d'exécution. Spécifiquement,
pro::facade_builder : offre la capacité de construire un type de façade au moment de la compilation.add_convention : ajoute une "convention d'appel" généralisée, définie par une "répartition" et plusieurs "surcharges", au contexte de construction.pro::operator_dispatch <"<<", true> : spécifie une répartition pour l'opérateur << des expressions où l'opérande principal ( proxy ) est sur le côté droit (spécifié par le deuxième paramètre de modèle true ). Notez que le polymorphisme dans la bibliothèque "proxy" est défini par les expressions plutôt que par les fonctions membres, qui est différente des fonctions virtuelles C ++ ou d'autres langages OOP.std::ostream&(std::ostream& out) const : La signature de la convention d'appel, similaire avec std::move_only_function . const spécifie que l'opérande principal est const .build : construit le contexte en un type de façade. pro::proxy <Streamable> p1 = &str : crée un objet proxy à partir d'un pointeur brut de std::string . p1 se comporte comme un pointeur brut et n'a pas la propriété de la std::string jacente. Si la durée de vie de str se termine avant p1 , p1 devient pendante.
std::cout << *p1 : c'est ainsi que cela fonctionne. Il imprime "Hello World" parce que la convention d'appel est définie dans le Streamable de façade, donc il fonctionne comme en appelant std::cout << str .
pro::proxy <Streamable> p2 = std::make_unique <int>(123) : crée un std::unique_ptr <int> et se convertit en proxy . Différent de p1 , p2 est propriétaire de l' int sous-jacent car il est instancié à partir d'une valeur de std::unique_ptr , et appellera le destructeur de std::unique_ptr lorsque p2 est détruit, tandis que p1 n'a pas la possession de l' int sous-jacent car il est instancié à partir d'un pointeur brut. p1 et p2 sont du même type pro::proxy<Streamable> , ce qui signifie que vous pouvez avoir une fonction qui renvoie pro::proxy<Streamable> sans exposer aucune information sur les détails d'implémentation à son appelant.
std::cout << *p2 : imprime "123" sans surprise.
pro::proxy <Streamable> p3 = pro::make_proxy <Streamable>(3.14) : crée un proxy à partir d'un double sans spécifier le type de pointeur sous-jacent. Spécifiquement,
p2 , p3 est également propriétaire de la double valeur sous-jacente, mais peut éviter efficacement l'allocation de tas.double ) est connue pour être petite (sur les principales plates-formes 32 ou 64 bits), pro::make_proxy réalise le fait à Compile-Time, et retombe à pro::make_proxy_inplace , qui ne garantit pas d'allocation de tas.std::function et d'autres emballages polymorphes existants dans la norme. std::cout << *p3 : imprime "3.14" sans surprise.
Lorsque les retours main , p2 et p3 détruiront les objets sous-jacents, tandis que p1 ne fait rien car il contient un pointeur brut qui n'a pas la propriété de la std::string sous-jacente.
En plus des expressions d'opérateur démontrées dans l'exemple précédent, la bibliothèque prend en charge presque toutes les formes d'expressions en C ++ et peut les rendre polymorphes. Spécifiquement,
PRO_DEF_MEM_DISPATCH : définit un type de répartition pour les expressions d'appel de fonction membre.PRO_DEF_FREE_DISPATCH : définit un type de répartition pour les expressions d'appel de fonction gratuites.pro::operator_dispatch : Type de répartition pour les expressions de l'opérateur.pro::conversion_dispatch : Type de répartition pour les expressions de conversion.Notez que certaines installations sont fournies sous forme de macro, car les modèles C ++ ne prennent pas en charge la génération d'une fonction avec un nom arbitraire. Voici un autre exemple qui rend les expressions d'appel de fonction des membres polymorphes:
# 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"
}Voici une explication étape par étape:
#include <iostream> : pour std::cout .#include <sstream> : pour std::stringstream .#include "proxy.h" : pour la bibliothèque "proxy".PRO_DEF_MEM_DISPATCH (MemDraw, Draw) : définit un type de répartition MemDraw pour les expressions de la fonction membre de la fonction membre DrawPRO_DEF_MEM_DISPATCH (MemArea, Area) : définit une MemArea de type d'expédition pour les expressions de Area de fonction des membres d'appel.struct Drawable : pro::facade_builder ... ::build {} : définit un type de façade Drawable . Spécifiquement,add_convention : ajoute des conventions d'appel au contexte de construction.support_copy < pro::constraint_level ::nontrivial> : Spécifie le type de pointeur sous-jacent doit être copyable, ce qui rend également le type de proxy résultant.class Rectangle : une implémentation de Drawable .PrintDrawableToString : convertit un Drawable en une std::string . Notez qu'il s'agit d'une fonction plutôt que d'un modèle de fonction, ce qui signifie qu'il peut générer ABI dans un système de construction plus grand.pro::proxy<Drawable> p = pro::make_proxy<Drawable, Rectangle>(3, 5) : crée un objet proxy<Drawable> contenant un Rectangle .std::string str = PrintDrawableToString(p) : convertit p en une std::string , crée implicitement une copie de p .std::cout << str : imprime la chaîne.La bibliothèque "Proxy" est une solution autonome pour le polymorphisme d'exécution en C ++. Il existe de nombreuses autres capacités documentées dans les spécifications. En plus des fonctionnalités mentionnées ci-dessus, voici une liste organisée des fonctionnalités les plus populaires basées sur les commentaires des utilisateurs:
facade_builder::add_convention est plus puissant que démontré ci-dessus. Il peut prendre n'importe quel nombre de types de surcharge (officiellement, tout type répondant aux exigences de chargement ) et effectuer une résolution de surcharge standard lors de l'invocation d'un proxy .facade_builder::add_facade permet une composition flexible de différentes abstractions.PRO_DEF_WEAK_DISPATCH à partir d'un type de répartition existant et d'une implémentation par défaut.allocate_proxy est capable de créer un proxy à partir d'une valeur avec n'importe quel allocateur personnalisé. Dans C ++ 11, std::function et std::packaged_task avaient des constructeurs qui ont accepté les allocateurs personnalisés pour le réglage des performances, mais ceux-ci ont été supprimés dans C ++ 17 parce que "la sémantique n'est pas claire, et il y a des problèmes techniques avec le stockage d'un allocateur dans un contexte de type type, puis de récupérer cet allocateur plus tard pour tous les allocations nécessaires lors de l'affectation de copie". Ces problèmes ne s'appliquent pas à allocate_proxy .facade_builder fournit une prise en charge complète pour la configuration des contraintes, y compris la mise en page de la mémoire (par restrict_layout ), la copie (par support_copy ), la relocatabilité (par support_relocation ) et la destructibilité (par support_destruction ).proxy prend en charge la réflexion sur le temps de compilation basée sur le type pour les requêtes d'exécution. Veuillez vous référer à facade_builder::add_reflection ET MODÈLE DE FONCTION proxy_reflect pour plus de détails. | Famille | Version minimale | Drapeaux requis |
|---|---|---|
| GCC | 13.1 | -std = c ++ 20 |
| Bruit | 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
Ce projet accueille les contributions et les suggestions. La plupart des contributions vous obligent à accepter un accord de licence de contributeur (CLA) déclarant que vous avez le droit de faire et en fait, accordez-nous les droits d'utilisation de votre contribution. Pour plus de détails, visitez https://cla.opensource.microsoft.com.
Lorsque vous soumettez une demande de traction, un bot CLA déterminera automatiquement si vous devez fournir un CLA et décorer le RP de manière appropriée (par exemple, vérification d'état, commentaire). Suivez simplement les instructions fournies par le bot. Vous n'aurez besoin de le faire qu'une seule fois sur tous les dépositions en utilisant notre CLA.
Ce projet a adopté le code de conduite open source Microsoft. Pour plus d'informations, consultez le code de conduite FAQ ou contactez [email protected] avec toute question ou commentaire supplémentaire.
Ce projet peut contenir des marques ou des logos pour des projets, des produits ou des services. L'utilisation autorisée de marques ou de logos Microsoft est soumise et doit suivre les directives de marque et de marque de Microsoft. L'utilisation de marques ou de logos de Microsoft dans des versions modifiées de ce projet ne doit pas provoquer de confusion ou impliquer le parrainage de Microsoft. Toute utilisation de marques ou de logos tiers est soumis aux politiques de ces tiers.