هل تستخدم boost::variant
أو أحد تطبيقات C ++ 11 المفتوحة للمصادر المفتوحة لـ "الاتحاد الموسومة" أو النوع المتغير في مشاريع C ++ الخاصة بك؟
boost::variant
هي مكتبة رائعة. لقد قمت بإنشاء strict_variant
من أجل معالجة بعض الأشياء حول boost::variant
التي لم يعجبني.
إصدار TL ؛ DR هو أنه على عكس boost::variant
أو std::variant
، لن يلقي strict_variant
أبدًا استثناءًا أو يقدم تخصيصًا ديناميكيًا في جهد أنواع الدعم التي لها تحركات رمي. سوف تفشل الإصدار الافتراضي ببساطة تأكيدًا ثابتًا إذا حدث هذا. سوف يقوم strict_variant::easy_variant
بتخصيص مخصصات في هذا الموقف ، حتى تتمكن من الاشتراك في ذلك إذا أردت ، وهذين الإصداران من البديل "يلعب بشكل جيد" معًا. غالبًا ما يكون هذا النوع من الأشياء مصدر قلق كبير في المشاريع ذات المتطلبات الحقيقية ، أو في الأجهزة المدمجة ، والتي قد لا تسمح ، أو قد لا تحتوي ببساطة على ميزات C ++ هذه. إذا كنت تقوم بعمل مكتبة يمكن استخدامها في المشاريع "التقليدية" التي تريد سهولة الاستخدام التي تأتي من boost::variant
، ولكن يمكن أيضًا استخدامها في المشاريع ذات المتطلبات التقييدية ، وتريد استخدام نوع variant
كجزء من واجهة برمجة التطبيقات ، قد يوفر 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;
}
The 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()
قد يكون هذا مفاجئًا جدًا لبعض المبرمجين.
على النقيض من ذلك ، إذا كنت تستخدم C ++ 17 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]
على صفحات جيثب.
يستهدف strict_variant
معيار C ++ 11.
من المعروف أن العمل مع gcc >= 4.8
و clang >= 3.5
، ويتم اختباره مقابل MSVC 2015
.
يمكن استخدام strict_variant
كما هو في المشاريع التي تتطلب -fno-exceptions
و -fno-rtti
.
متغير صارم متاح بموجب ترخيص برنامج Boost.