คุณต้องการทำให้การจัดการและการบำรุงรักษาวัตถุ polymorphic ง่ายขึ้นใน C ++ หรือไม่?
คุณต้องการเขียนรหัส polymorphic ใน C ++ ได้อย่างง่ายดายเช่นเดียวกับภาษา GC เช่น Java หรือ C#โดยไม่ต้องเสียสละประสิทธิภาพหรือไม่?
คุณเคยลองไลบรารีการเขียนโปรแกรม polymorphic อื่น ๆ ใน C ++ แต่พบว่าพวกเขาขาดหรือไม่?
ถ้าเป็นเช่นนั้นห้องสมุดนี้เหมาะสำหรับคุณ
"พร็อกซี" เป็นห้องสมุด C ++ ที่ทันสมัยที่ช่วยให้คุณใช้ polymorphism (วิธีการใช้วัตถุประเภทต่าง ๆ แทนกันได้) โดยไม่จำเป็นต้องได้รับมรดก
"พร็อกซี" ถูกสร้างขึ้นโดยวิศวกรของ Microsoft และถูกนำมาใช้ในระบบปฏิบัติการ Windows มาตั้งแต่ปี 2565 เป็นเวลาหลายปีการใช้มรดกเป็นวิธีหลักในการบรรลุความหลากหลายใน C ++ อย่างไรก็ตามภาษาการเขียนโปรแกรมใหม่เช่น Rust เสนอวิธีที่ดีกว่าในการทำเช่นนี้ เราได้ปรับปรุงความเข้าใจของเราเกี่ยวกับการเขียนโปรแกรมเชิงวัตถุและตัดสินใจใช้ พอยน์เตอร์ ใน C ++ เป็นรากฐานสำหรับ "พร็อกซี" โดยเฉพาะอย่างยิ่งห้องสมุด "พร็อกซี" ได้รับการออกแบบให้เป็น:
โปรดดูคำถามที่พบบ่อยของพร็อกซีสำหรับพื้นหลังเพิ่มเติมและอ้างอิงข้อกำหนดสำหรับรายละเอียดทางเทคนิคเพิ่มเติม
"พร็อกซี" เป็นห้องสมุด C ++ 20 ส่วนหัวเท่านั้น ในการใช้ห้องสมุดตรวจสอบให้แน่ใจว่าคอมไพเลอร์ของคุณตรงตามข้อกำหนดขั้นต่ำและรวมไฟล์ส่วนหัว proxy.h ในซอร์สโค้ดของคุณ หรือคุณสามารถติดตั้งไลบรารีผ่าน VCPKG หรือ Conan โดยค้นหา "พร็อกซี" (ดู 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> : สำหรับ std::cout
#include <string> : สำหรับ 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 ) โปรดทราบว่า polymorphism ในไลบรารี "พร็อกซี" ถูกกำหนดโดยนิพจน์มากกว่าฟังก์ชั่นสมาชิกซึ่งแตกต่างจากฟังก์ชั่นเสมือน C ++ หรือภาษา OOP อื่น ๆstd::ostream&(std::ostream& out) const : ลายเซ็นของการประชุมการเรียกคล้ายกับ std::move_only_function const ระบุว่าตัวถูกดำเนินการหลักคือ constbuild : สร้างบริบทเป็นประเภทด้านหน้า pro::proxy <Streamable> p1 = &str : สร้างวัตถุ proxy จากตัวชี้ดิบของ 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 พื้นฐาน 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 ++ และสามารถทำให้เป็น polymorphic โดยเฉพาะ
PRO_DEF_MEM_DISPATCH : กำหนดประเภทการจัดส่งสำหรับการแสดงออกของฟังก์ชันสมาชิกPRO_DEF_FREE_DISPATCH : กำหนดประเภทการจัดส่งสำหรับนิพจน์การโทรฟังก์ชั่นฟรีpro::operator_dispatch : ประเภทการจัดส่งสำหรับนิพจน์ของผู้ปฏิบัติงานpro::conversion_dispatch : ประเภทการจัดส่งสำหรับนิพจน์การแปลงโปรดทราบว่าสิ่งอำนวยความสะดวกบางอย่างมีให้เป็นมาโครเนื่องจากเทมเพลต C ++ วันนี้ไม่สนับสนุนการสร้างฟังก์ชั่นที่มีชื่อโดยพลการ นี่เป็นอีกตัวอย่างหนึ่งที่ทำให้การเรียกใช้ฟังก์ชันการเรียกใช้ฟังก์ชันการเรียก polymorphic:
# 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> : สำหรับ std::cout#include <sstream> : สำหรับ std::stringstream#include "proxy.h" : สำหรับห้องสมุด "พร็อกซี"PRO_DEF_MEM_DISPATCH (MemDraw, Draw) : กำหนด MemDraw สีประเภทการส่งสำหรับการแสดงออกของฟังก์ชั่น Draw โทรPRO_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 : การใช้งานของ DrawablePrintDrawableToString : แปลง Drawable เป็น std::string โปรดทราบว่านี่เป็นฟังก์ชันแทนที่จะเป็นเทมเพลตฟังก์ชั่นซึ่งหมายความว่าสามารถสร้าง ABI ในระบบบิลด์ขนาดใหญ่pro::proxy<Drawable> p = pro::make_proxy<Drawable, Rectangle>(3, 5) : สร้างวัตถุ proxy<Drawable> ที่มีรูป Rectanglestd::string str = PrintDrawableToString(p) : แปลง p เป็น std::string โดยปริยายสร้างสำเนาของ pstd::cout << str : พิมพ์สตริงไลบรารี "พร็อกซี" เป็นวิธีแก้ปัญหาที่มีอยู่ในตัวเองสำหรับความหลากหลายของรันไทม์ใน C ++ มีความสามารถอื่น ๆ อีกมากมายที่บันทึกไว้ในข้อกำหนด นอกเหนือจากคุณสมบัติที่กล่าวถึงข้างต้นนี่คือรายการที่ได้รับความนิยมมากที่สุดตามข้อเสนอแนะของผู้ใช้:
facade_builder::add_convention มีประสิทธิภาพมากกว่าที่แสดงไว้ข้างต้น สามารถใช้ประเภทของโอเวอร์โหลดได้ทุกประเภท (อย่างเป็นทางการประเภทใด ๆ ที่ตรงตามข้อกำหนดของ prooverload ) และดำเนินการความละเอียดเกินพิกัดมาตรฐานเมื่อเรียกใช้ proxyfacade_builder::add_facade ช่วยให้องค์ประกอบที่ยืดหยุ่นของ abstractions ที่แตกต่างกันPRO_DEF_WEAK_DISPATCH จากประเภทการจัดส่งที่มีอยู่และการใช้งานเริ่มต้นallocate_proxy สามารถสร้าง proxy จากค่าที่มีการจัดสรรแบบกำหนดเองใด ๆ ใน C ++ 11, std::function และ std::packaged_task มีตัวสร้างที่ยอมรับการจัดสรรแบบกำหนดเองสำหรับการปรับแต่งประสิทธิภาพ แต่สิ่งเหล่านี้ถูกลบออกใน C ++ 17 เพราะ "ความหมายไม่ชัดเจนและมีปัญหาทางเทคนิคเกี่ยวกับการจัดสรรการจัดสรรในบริบท ปัญหาเหล่านี้ใช้ไม่ได้กับ allocate_proxyfacade_builder ให้การสนับสนุนอย่างเต็มที่สำหรับการกำหนดค่าข้อ จำกัด รวมถึงเค้าโครงหน่วยความจำ (โดย restrict_layout ), การคัดลอก (โดย support_copy ), relocatability (โดย support_relocation ) และการทำลายล้าง (โดย support_destruction )proxy รองรับการสะท้อนเวลารวบรวมตามประเภทสำหรับการสืบค้นรันไทม์ โปรดดูที่ facade_builder::add_reflection และเทมเพลตฟังก์ชัน proxy_reflect สำหรับรายละเอียดเพิ่มเติม | ตระกูล | เวอร์ชันขั้นต่ำ | ธงที่จำเป็น |
|---|---|---|
| GCC | 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 บอทจะพิจารณาโดยอัตโนมัติว่าคุณจำเป็นต้องให้ CLA และตกแต่ง PR อย่างเหมาะสม (เช่นการตรวจสอบสถานะแสดงความคิดเห็น) เพียงทำตามคำแนะนำที่จัดทำโดยบอท คุณจะต้องทำสิ่งนี้เพียงครั้งเดียวใน repos ทั้งหมดโดยใช้ CLA ของเรา
โครงการนี้ได้นำรหัสการดำเนินงานของ Microsoft โอเพ่นซอร์สมาใช้ สำหรับข้อมูลเพิ่มเติมโปรดดูจรรยาบรรณคำถามที่พบบ่อยหรือติดต่อ [email protected] พร้อมคำถามหรือความคิดเห็นเพิ่มเติมใด ๆ
โครงการนี้อาจมีเครื่องหมายการค้าหรือโลโก้สำหรับโครงการผลิตภัณฑ์หรือบริการ การใช้เครื่องหมายการค้าหรือโลโก้ของ Microsoft ที่ได้รับอนุญาตขึ้นอยู่กับและต้องปฏิบัติตามแนวทางเครื่องหมายการค้าและแบรนด์ของ Microsoft การใช้เครื่องหมายการค้าหรือโลโก้ของ Microsoft ในรุ่นที่แก้ไขของโครงการนี้จะต้องไม่ทำให้เกิดความสับสนหรือบอกเป็นสปอนเซอร์ของ Microsoft การใช้เครื่องหมายการค้าหรือโลโก้ของบุคคลที่สามจะอยู่ภายใต้นโยบายของบุคคลที่สามเหล่านั้น