คุณใช้ boost::variant
หรือหนึ่งในการใช้งาน C ++ 11 Open-Source C ++ ของ "Tagged Union" หรือประเภทตัวแปรในโครงการ C ++ ของคุณหรือไม่?
boost::variant
เป็นห้องสมุดที่ยอดเยี่ยม ฉันสร้าง strict_variant
เพื่อที่จะกล่าวถึงบางสิ่งเกี่ยวกับ boost::variant
ที่ฉันไม่ชอบ
เวอร์ชัน TL; DR นั้นไม่เหมือน boost::variant
หรือ std::variant
, strict_variant
จะไม่โยนข้อยกเว้นหรือทำการจัดสรรแบบไดนามิกในความพยายามในการสนับสนุนประเภทที่มีการเคลื่อนไหว เวอร์ชันเริ่มต้นจะล้มเหลวในการยืนยันแบบคงที่หากสิ่งนี้จะเกิดขึ้น strict_variant::easy_variant
จะทำการจัดสรรในสถานการณ์นี้ดังนั้นคุณสามารถเลือกใช้สิ่งนั้นได้หากคุณต้องการและตัวแปรทั้งสองรุ่นนี้ "เล่นได้ดี" ด้วยกัน สิ่งนี้มักจะเป็นปัญหาสำคัญในโครงการที่มีข้อกำหนดเรียลไทม์หรือในอุปกรณ์ฝังตัวซึ่งอาจไม่อนุญาตหรืออาจไม่มีคุณสมบัติ C ++ เหล่านี้ หากคุณกำลังสร้างห้องสมุดที่อาจใช้ในโครงการ "ธรรมดา" ที่ต้องการความสะดวกในการใช้งานที่มาจาก boost::variant
แต่อาจใช้ในโครงการ variant
มีข้อกำหนดที่เข้มงวด ในฐานะส่วนหนึ่งของ API strict_variant
อาจเสนอวิธีที่จะทำให้ทุกคนมีความสุข
นอกจากนี้ยังมีปัญหาบางอย่างในอินเทอร์เฟซของตัวแปรที่ได้รับการแก้ไขซึ่งทำให้การใช้ IMHO แบบวันต่อวัน (จริง ๆ แล้วเป็นแรงจูงใจดั้งเดิมของโครงการ)
ฉันไม่ชอบรหัสนั้นเช่นนี้อาจรวบรวมได้โดยไม่มีข้อความเตือนหรือข้อความแสดงข้อผิดพลาด:
boost::variant<std::string, int > v;
v = true ;
ฉันมักจะค่อนข้างว่า variant
ของฉันมีความเข้มงวดมากขึ้นเกี่ยวกับการแปลงโดยนัยที่สามารถเกิดขึ้นได้
ฉัน ต้องการ ให้สิ่งนี้ควรรวบรวมและทำสิ่งที่สมเหตุสมผลแม้ว่าการแก้ปัญหาการโอเวอร์โหลดจะคลุมเครือ
variant< bool , long , double , std::string> v;
v = true ; // selects bool
v = 10 ; // selects long
v = 20 . 5f ; // selects double
v = " foo " ; // selects string
ฉันยังต้องการให้พฤติกรรมดังกล่าว (สิ่งที่เลือกในกรณีดังกล่าว) เป็นแบบพกพา
(สำหรับตัวอย่างรหัสเช่นนี้ที่ boost::variant
มีพฤติกรรมที่โชคร้ายดู "นามธรรมและแรงจูงใจ" ในเอกสารประกอบ)
ใน strict_variant
เราแก้ไขความละเอียดมากเกินไปในสถานการณ์เหล่านี้โดยการลบผู้สมัครบางคน
ตัวอย่างเช่น:
bool
, อินทิกรัล, จุดลอยตัว, ตัวชี้, ตัวละครและคลาสอื่น ๆint -> long
และ int -> long long
เป็นผู้สมัคร long long
จะถูกกำจัดดูเอกสารสำหรับรายละเอียด
ฉันไม่ชอบ boost::variant
จะทำสำเนาสำรองของวัตถุอย่างเงียบ ๆ ตัวอย่างเช่นพิจารณาโปรแกรมง่าย ๆ นี้ซึ่งมีการกำหนด A
และ B
เพื่อบันทึกการโทร CTOR และ DTOR ทั้งหมด
int main () {
using var_t = boost::variant<A, B>;
var_t v{ A ()};
std::cout << " 1 " << std::endl;
v = B ();
std::cout << " 2 " << std::endl;
v = A ();
std::cout << " 3 " << std::endl;
}
boost::variant
สร้างผลลัพธ์ต่อไปนี้:
A ()
A(A&&)
~A()
1
B()
B(B&&)
A( const A &)
~A()
B( const B &)
~A()
~B()
~B()
2
A()
A(A&&)
B( const B &)
~B()
A( const A &)
~B()
~A()
~A()
3
~A()
นี่อาจเป็นเรื่องที่น่าแปลกใจสำหรับโปรแกรมเมอร์บางคน
ในทางตรงกันข้ามถ้าคุณใช้ std::variant
หรือ std::experimental::variant
ในตัวแปรที่มีความหมาย "บางครั้ง-ว่างเปล่า" คุณจะได้รับสิ่งนี้
A ()
A(A&&)
~A()
1
B()
~A()
B(B&&)
~B()
2
A()
~B()
A(A&&)
~A()
3
~A()
นี่คือสิ่งที่ใกล้เคียงกับสิ่งที่โปรแกรมเมอร์ไร้เดียงสาคาดหวังว่าใครไม่ทราบรายละเอียดภายในของ boost::variant
- สำเนาวัตถุเดียวของเขาที่มีอยู่คือสิ่งที่เขาเห็นในซอร์สโค้ดของเขา
สิ่งนี้มักจะไม่สำคัญ แต่บางครั้งถ้าตัวอย่างเช่นคุณกำลังแก้ไขปัญหาการทุจริตหน่วยความจำที่น่ารังเกียจ (อาจมีรหัสที่ไม่ดีในหนึ่งในวัตถุที่มีอยู่ในตัวแปร) จากนั้นวัตถุพิเศษการเคลื่อนไหวและสำเนาเหล่านี้อาจ ทำให้สิ่งต่าง ๆ ซับซ้อนขึ้นโดยบังเอิญ
นี่คือสิ่งที่คุณได้รับด้วย strict_variant
:
A ()
A(A&&)
~A()
1
B()
B(B&&)
~A()
~B()
2
A()
A(A&&)
~B()
~A()
3
~A()
แต่ strict_variant
ไม่มีสถานะว่างเปล่าและปลอดภัยอย่างเต็มที่!
(ตัวอย่างเหล่านี้จาก gcc 5.4
ดูรหัสในโฟลเดอร์ example
)
เพื่อสรุปความแตกต่าง:
std::variant
ไม่ค่อยว่างเปล่ามักจะใช้สแต็ค อันที่จริงมันว่างเปล่าเมื่อมีการโยนข้อยกเว้น ในภายหลังมันจะมีข้อยกเว้นที่แตกต่างกันหากคุณพยายามเยี่ยมชมเมื่อว่างเปล่าboost::variant
ไม่ว่างเปล่ามักจะใช้สแต็ค มันจะต้องทำการจัดสรรแบบไดนามิกและสำเนาสำรองเมื่อใดก็ตามที่มีข้อยกเว้น สามารถ โยนได้strict_variant
ไม่ว่างเปล่าและใช้สแต็กอย่างแน่นอนเมื่อประเภทมูลค่าปัจจุบันไม่สามารถเคลื่อนย้ายได้ มันไม่เคยทำให้การสำรองข้อมูลหรือคัดลอกและไม่เคยทำข้อยกเว้น แต่ละวิธีมีข้อดี ฉันเลือกวิธีการ strict_variant
เพราะฉันพบว่ามันง่ายกว่าและมันหลีกเลี่ยงสิ่งที่ฉันคิดว่าเป็นข้อเสียของ boost::variant
และ std::variant
และถ้าคุณจัดการเพื่อให้ทุกประเภทของคุณไม่สามารถเคลื่อนไหวได้ซึ่งฉันมักจะพบว่าฉันสามารถทำได้ดังนั้น strict_variant
จะให้ประสิทธิภาพที่ดีที่สุดแก่คุณเช่นเดียวกับ std::variant
โดยไม่มีสถานะว่างเปล่า
สำหรับการอภิปรายเชิงลึกเกี่ยวกับการออกแบบตรวจสอบเอกสาร
สำหรับบทนำที่อ่อนโยนต่อตัวแปรและภาพรวมของตัวแปรที่เข้มงวดดู สไลด์ จากการพูดคุยที่ฉันให้เกี่ยวกับเรื่องนี้: [pptx] [PDF]
บนหน้า GitHub
strict_variant
กำหนดเป้าหมายมาตรฐาน C ++ 11
เป็นที่ทราบกันดีว่าทำงานกับ gcc >= 4.8
และ clang >= 3.5
และทดสอบกับ MSVC 2015
strict_variant
สามารถใช้ตามที่เป็นอยู่ในโครงการที่ต้องการ -fno-exceptions
และ -fno-rtti
ตัวแปรที่เข้มงวด สามารถใช้ได้ภายใต้ใบอนุญาตซอฟต์แวร์ Boost