هل تتطلع إلى تبسيط إدارة مدى الحياة وصيانة الكائنات متعددة الأشكال في C ++؟
هل تريد كتابة رمز متعدد الأشكال في C ++ بسهولة كما هو الحال في لغات GC مثل Java أو C#، دون التضحية بالأداء؟
هل جربت مكتبات البرمجة متعددة الأشكال الأخرى في C ++ ولكن وجدتها ناقصة؟
إذا كان الأمر كذلك ، فهذه المكتبة لك.
"Proxy" هي مكتبة C ++ حديثة تساعدك على استخدام تعدد الأشكال (طريقة لاستخدام أنواع مختلفة من الكائنات بالتبادل) دون الحاجة إلى الميراث.
تم إنشاء "Proxy" من قبل Microsoft Engineers وتم استخدامه في نظام تشغيل Windows منذ عام 2022. لسنوات عديدة ، كان استخدام الميراث هو الطريقة الرئيسية لتحقيق تعدد الأشكال في C ++. ومع ذلك ، فإن لغات البرمجة الجديدة مثل Rust توفر طرقًا أفضل للقيام بذلك. لقد قمنا بتحسين فهمنا للبرمجة الموجهة للكائنات وقررنا استخدام مؤشرات في C ++ كأساس لـ "الوكيل". على وجه التحديد ، تم تصميم مكتبة "الوكيل" لتكون:
يرجى الرجوع إلى أسئلة الوكيل بشكل متكرر لمزيد من الخلفية ، والإشارة إلى المواصفات للحصول على مزيد من التفاصيل الفنية.
"Proxy" هي مكتبة C ++ 20 رأسًا. لاستخدام المكتبة ، تأكد من أن المترجم الخاص بك يفي بالحد الأدنى من المتطلبات وتضمين فقط Proxy.H في رمز المصدر الخاص بك. بدلاً من ذلك ، يمكنك تثبيت المكتبة عبر VCPKG أو Conan ، من خلال البحث عن "Proxy" (انظر VCPKG.io و Conan.io).
دعنا نبدأ مع مثال "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"
}فيما يلي تفسير خطوة بخطوة:
#include <iostream> : for std::cout .
#include <string> : for std::string .
#include "proxy.h" : لمكتبة "الوكيل". يتم تعريف معظم منشآت المكتبة في pro الاسم. إذا تم استهلاك المكتبة عبر VCPKG أو Conan ، فيجب تغيير هذا الخط إلى #include <proxy/proxy.h> .
struct Streamable : pro::facade_builder ... ::build {} : يحدد نوع واجهة Streamable . إن مصطلح "واجهة" ، والذي يُعرَّف رسميًا بأنه متطلبات profacade ، هو كيف تجريد وقت تشغيل مكتبة "الوكيل". خاصة،
pro::facade_builder : يوفر القدرة على إنشاء نوع واجهة في وقت الترجمة.add_convention : يضيف "اتفاقية استدعاء" معممة ، محددة بواسطة "إرسال" والعديد من "الأحمال الزائدة" ، إلى سياق الإنشاء.pro::operator_dispatch <"<<", true> : يحدد إرسال للمشغل << التعبيرات حيث يكون المعامل الأساسي ( proxy ) على الجانب الأيمن (المحدد بواسطة معلمة القالب الثاني true ). لاحظ أن تعدد الأشكال في مكتبة "الوكيل" يتم تعريفه بواسطة التعبيرات بدلاً من وظائف الأعضاء ، والتي تختلف عن وظائف C ++ الافتراضية أو لغات OOP الأخرى.std::ostream&(std::ostream& out) const : توقيع اتفاقية الاتصال ، على غرار std::move_only_function . يحدد const أن المعامل الأساسي هو const .build : يبني السياق في نوع واجهة. pro::proxy <Streamable> p1 = &str : إنشاء كائن proxy من مؤشر RAW من std::string . يتصرف p1 مثل مؤشر خام ، وليس له ملكية std::string . إذا انتهى عمر str قبل p1 ، يصبح p1 متدليًا.
std::cout << *p1 : هذه هي الطريقة التي تعمل بها. إنه يطبع "Hello World" لأن اتفاقية الاتصال محددة في الواجهة Streamable ، لذلك فهي تعمل كما لو كانت عن طريق الاتصال std::cout << str .
pro::proxy <Streamable> p2 = std::make_unique <int>(123) : ينشئ std::unique_ptr <int> ويتحول إلى proxy . يختلف عن p1 ، يتمتع p2 بملكية int الأساسية لأنه تم إنشاء مثيل له من قيمة std::unique_ptr ، وسوف يطلق على مدمر std::unique_ptr عندما يتم تدمير p2 ، في حين أن p1 لا تملك ملكية int الأساسية لأنه تم إنشاءه من مؤشر RAW. p1 و p2 من نفس النوع pro::proxy<Streamable> ، مما يعني أنه يمكنك الحصول على وظيفة تقوم بإرجاع pro::proxy<Streamable> دون تعريض أي معلومات حول تفاصيل التنفيذ إلى المتصل.
std::cout << *p2 : المطبوعات "123" دون مفاجأة.
pro::proxy <Streamable> p3 = pro::make_proxy <Streamable>(3.14) : إنشاء proxy من double دون تحديد نوع المؤشر الأساسي. خاصة،
p2 ، يتمتع p3 أيضًا بملكية القيمة double الأساسية ، ولكن يمكنه تجنب تخصيص الكومة بشكل فعال.double ) معروف بأنه صغير (على منصات 32 أو 64 بت الرئيسية) ، يدرك pro::make_proxy الحقيقة في وقت الترجمة ، ويعود إلى pro::make_proxy_inplace ، مما يضمن عدم تخصيص الكومة.std::function وغيرها من الأغلفة المتعددة الأشكال الموجودة في المعيار. std::cout << *p3 : المطبوعات "3.14" دون مفاجأة.
عندما يعود main ، سيدمر p2 و p3 الأشياء الأساسية ، في حين أن p1 لا يفعل شيئًا لأنه يحمل مؤشرًا خامًا ليس له ملكية std::string الأساسية.
بالإضافة إلى تعبيرات المشغل الموضحة في المثال السابق ، تدعم المكتبة جميع أشكال التعبيرات تقريبًا في C ++ ويمكن أن تجعلها متعددة الأشكال. خاصة،
PRO_DEF_MEM_DISPATCH : يحدد نوع الإرسال لتعبيرات استدعاء دالة الأعضاء.PRO_DEF_FREE_DISPATCH : يحدد نوع الإرسال لتعبيرات استدعاء الوظيفة المجانية.pro::operator_dispatch : نوع الإرسال لتعبيرات المشغل.pro::conversion_dispatch : نوع الإرسال لتعبيرات التحويل.لاحظ أنه يتم توفير بعض المنشآت كماكرو ، لأن قوالب C ++ اليوم لا تدعم توليد وظيفة باسم تعسفي. فيما يلي مثال آخر يجعل تعبيرات استدعاء وظيفة الأعضاء تعدد الأشكال:
# 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"
}فيما يلي تفسير خطوة بخطوة:
#include <iostream> : for std::cout .#include <sstream> : for std::stringstream .#include "proxy.h" : لمكتبة "الوكيل".PRO_DEF_MEM_DISPATCH (MemDraw, Draw) : يحدد نوع إرسال MemDraw لتعبيرات استدعاء وظيفة العضو DrawPRO_DEF_MEM_DISPATCH (MemArea, Area) : يحدد MemArea نوع الإرسال للتعبير عن Area وظيفة أعضاء الاتصال.struct Drawable : pro::facade_builder ... ::build {} : يحدد نوع الواجهة Drawable . خاصة،add_convention : يضيف اتفاقيات الاتصال إلى سياق الإنشاء.support_copy < pro::constraint_level ::nontrivial> : يحدد نوع المؤشر الأساسي قابل للنسخ ، مما يجعل أيضًا نوع proxy الناتج.class Rectangle : تنفيذ Drawable .PrintDrawableToString : يحول رسم Drawable إلى std::string . لاحظ أن هذه وظيفة بدلاً من قالب الوظيفة ، مما يعني أنه يمكن أن يولد ABI في نظام بناء أكبر.pro::proxy<Drawable> p = pro::make_proxy<Drawable, Rectangle>(3, 5) : ينشئ كائن proxy<Drawable> يحتوي على Rectangle .std::string str = PrintDrawableToString(p) : تحويل p إلى std::string ، ينشئ ضمنيًا نسخة من p .std::cout << str : يطبع السلسلة.تعد مكتبة "الوكيل" حلاً مكتوبًا بذاتها في تعدد الأشكال في وقت التشغيل في C ++. هناك العديد من القدرات الأخرى الموثقة في المواصفات. بالإضافة إلى الميزات المذكورة أعلاه ، إليك قائمة منسقة من الميزات الأكثر شعبية على أساس ملاحظات المستخدم:
facade_builder::add_convention أقوى مما هو موضح أعلاه. قد يستغرق الأمر أي عدد من أنواع الحمل الزائد (رسميًا ، أي نوع يفي بمتطلبات Prooverload ) ويؤدي دقة التحميل الزائد القياسي عند استدعاء proxy .facade_builder::add_facade يسمح بتكوين مرن من التجريدات المختلفة.PRO_DEF_WEAK_DISPATCH من نوع إرسال موجود وتطبيق افتراضي.allocate_proxy من إنشاء proxy من قيمة مع أي مخصص مخصص. في C ++ 11 ، std::function و std::packaged_task مُنشئين قبلوا المخصصين المخصصين لضبط الأداء ، ولكن تمت إزالتها في C ++ 17 لأن "الدلالات غير واضحة ، وهناك مشكلات فنية مع تخزين مخصص في سياق محدد ثم استرداد هذا التخصيص لاحقًا لأي مواقع متطلعة خلال نسخ النسخ. لا تنطبق هذه المشكلات على allocate_proxy .facade_builder دعمًا كاملاً لتكوين القيود ، بما في ذلك تخطيط الذاكرة (عن طريق restrict_layout ) ، قابلية النسخ (بواسطة support_copy ) ، القابلية للتنفيذ (بواسطة support_relocation ) ، والدمار (بواسطة support_destruction ).proxy انعكاس وقت الترجمة المستندة إلى النوع لاستعلامات وقت التشغيل. يرجى الرجوع إلى facade_builder::add_reflection و function proxy_reflect لمزيد من التفاصيل. | عائلة | الحد الأدنى للنسخة | أعلام مطلوبة |
|---|---|---|
| مجلس التعاون الخليجي | 13.1 | -std = C ++ 20 |
| كلانج | 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
يرحب هذا المشروع بالمساهمات والاقتراحات. تطلب منك معظم المساهمات الموافقة على اتفاقية ترخيص المساهم (CLA) مع إعلان أن لديك الحق في ذلك في الواقع ، ويفعلنا في الواقع حقوق استخدام مساهمتك. لمزيد من التفاصيل ، تفضل بزيارة https://cla.opensource.microsoft.com.
عند إرسال طلب سحب ، سيحدد CLA Bot تلقائيًا ما إذا كنت بحاجة إلى توفير CLA وتزيين العلاقات العامة بشكل مناسب (على سبيل المثال ، فحص الحالة ، التعليق). ببساطة اتبع الإرشادات التي يقدمها الروبوت. ستحتاج فقط إلى القيام بذلك مرة واحدة عبر جميع عمليات إعادة الشراء باستخدام CLA لدينا.
اعتمد هذا المشروع رمز سلوك المصدر المفتوح Microsoft. لمزيد من المعلومات ، راجع مدونة الشهادة الأسئلة الشائعة أو الاتصال بـ [email protected] مع أي أسئلة أو تعليقات إضافية.
قد يحتوي هذا المشروع على علامات تجارية أو شعارات للمشاريع أو المنتجات أو الخدمات. يخضع الاستخدام المعتمد للعلامات التجارية أو الشعارات Microsoft ويجب أن يتبعوا إرشادات Microsoft التجارية والعلامة التجارية. يجب ألا يسبب استخدام العلامات التجارية Microsoft أو الشعارات في إصدارات معدلة من هذا المشروع الارتباك أو يعني رعاية Microsoft. يخضع أي استخدام للعلامات التجارية أو الشعارات من طرف ثالث لسياسات تلك الطرف الثالث.