لماذا std :: type_info متعددة الأشكال؟
-
28-09-2019 - |
سؤال
هل هناك سبب لماذا std::type_info
تم تحديده ليكون متعدد الأشكال؟ تم تحديد المدمر ليكون افتراضيًا (وهناك تعليق على تأثير "بحيث يكون متعدد الأشكال" في تصميم وتطور C ++). لا أستطيع حقًا رؤية سبب مقنع. ليس لدي أي حالة استخدام محددة ، كنت أتساءل فقط عما إذا كان هناك من أي وقت مضى قصة منطقية أو وراءها.
إليكم بعض الأفكار التي توصلت إليها ورفضتها:
- إنها نقطة تمديد - قد تحدد التطبيقات الفئات الفرعية ، وقد تحاول البرامج بعد ذلك
dynamic_cast
أstd::type_info
لآخر ، النوع المشتق من قبل التنفيذ. ربما هذا هو السبب ، ولكن يبدو أنه من السهل على التطبيقات إضافة عضو محدد من قبل التنفيذ ، والذي قد يكون افتراضيًا. البرامج التي ترغب في اختبار هذه الامتدادات لن تكون بالضرورة غير قابلة للحمل على أي حال. - إنه لضمان تدمير الأنواع المشتقة بشكل صحيح عندما
delete
جي مؤشر قاعدة. ولكن لا توجد أنواع مشتقة قياسية ، لا يمكن للمستخدمين تحديد أنواع مشتقة مفيدة ، لأنهاtype_info
لا يوجد لديه منشئون عامون قياسيون ، وهكذاdelete
جيtype_info
المؤشر ليس قانونيًا ومحمولًا أبدًا. والأنواع المشتقة ليست مفيدة لأنها لا يمكن بناؤها - الاستخدام الوحيد الذي أعرفه لمثل هذه الأنواع المشتقة غير القابلة للبناء هو في تنفيذ أشياء مثلis_polymorphic
اكتب سمة. - يترك مفتوحة إمكانية metaclasses مع أنواع مخصصة - كل متعدد الأشكال
class A
سوف تحصل على "metaclass" مشتقةA__type_info
, التي تستمد منtype_info
. ربما يمكن أن تعرض مثل هذه الفصول المشتقة الأعضاء تلك الاتصالnew A
مع مختلف حجج المنشئ بطريقة آمنة من النوع ، وأشياء من هذا القبيل. لكن صنعtype_info
تعدد الأشكال نفسه في الواقع يجعل مثل هذه الفكرة مستحيلة بشكل أساسي ، لأنه يجب أن يكون لديك metaclasses ل metaclasses ، adinitum ، وهذا يمثل مشكلة إذا كان كلtype_info
الكائنات لها مدة تخزين ثابت. ربما باستثناء هذا هو السبب لجعله متعدد الأشكال. - هناك بعض الفائدة لتطبيق ميزات RTTI (بخلاف ذلك
dynamic_cast
) إلىstd::type_info
نفسه ، أو شخص ما اعتقد أنه كان لطيفًا ، أو محرجًا إذاtype_info
لم يكن متعدد الأشكال. ولكن بالنظر إلى أنه لا يوجد نوع مشتق قياسي ، ولا توجد فئات أخرى في التسلسل الهرمي القياسي والتي يمكن للمرء أن يجربها بشكل معقول ، فإن السؤال هو: ماذا؟ هل هناك فائدة للتعبيرات مثلtypeid(std::type_info) == typeid(typeid(A))
? - ذلك لأن المنفذين سيقومون بإنشاء نوع مستمد خاص بهم (كما أعتقد أن GCC تفعل). ولكن ، لماذا تهتم بتحديده؟ حتى إذا لم يتم تحديد المدمر على أنه افتراضي وقرر أحد المنافقون أنه ينبغي أن يكون ، بالتأكيد أن التنفيذ يمكن أن يعلن أنه افتراضي ، لأنه لا يغير مجموعة العمليات المسموح بها على
type_info
, ، لذلك لن يتمكن البرنامج المحمول من معرفة الفرق. - إنه شيء يتعلق بالمترجمين الذين يعانون من ABIs متوافق جزئيًا ، ربما نتيجة للربط الديناميكي. ربما يمكن للمنفذي التعرف على خاصة بهم
type_info
الفئة الفرعية (على عكس واحدة تنشأ من بائع آخر) بطريقة محمولة إذاtype_info
كان مضمونا لتكون افتراضية.
آخر واحد هو الأكثر منطقية بالنسبة لي في الوقت الحالي ، لكنه ضعيف للغاية.
المحلول
أفترض أنه موجود لراحة المنفذين. يسمح لهم بتحديد ممتد type_info
الفصول ، وحذفها من خلال المؤشرات إلى type_info
في خروج البرنامج ، دون الحاجة إلى البناء في سحر المترجم الخاص للاتصال بالمدمر الصحيح ، أو القفز عبر الأطواق.
من المؤكد أن هذا التنفيذ يمكن أن يعلن أنه افتراضي ، لأنه لا يغير مجموعة العمليات المسموح بها على type_info ، لذلك لن يتمكن البرنامج المحمول من معرفة الفرق.
لا أعتقد أن هذا صحيح. النظر في ما يلي:
#include <typeinfo>
struct A {
int x;
};
struct B {
int x;
};
int main() {
const A *a1 = dynamic_cast<const A*>(&typeid(int));
B b;
const A *a2 = dynamic_cast<const A*>(&b);
}
سواء كان ذلك مسؤول أم لا ، يُسمح بالقيام الأول بالديناميكي (ويتم تقييمه إلى مؤشر فارغ) ، في حين لا يُسمح بممثلي الديناميكي الثاني. حتى إذا type_info
تم تعريفه في المعيار للحصول على المدمرة غير الافتراضية ، لكن التنفيذ أضاف مدمرة افتراضية ، ثم يمكن لبرنامج محمول تحديد الفرق [*].
يبدو لي أبسط بالنسبة لي لوضع المدمرة الافتراضية في المعيار ، أكثر من:
أ) ضع ملاحظة في المعيار ، على الرغم من أن تعريف الفئة يعني ذلك type_info
لا يوجد لديه وظائف افتراضية ، يُسمح له أن يكون له مدمرة افتراضية.
ب) تحديد مجموعة البرامج التي يمكن أن تميز سواء type_info
هو متعدد الأشكال أم لا ، وحظرهم جميعا. قد لا تكون برامج مفيدة أو مثمرة للغاية ، لا أعرف ، ولكن لحظرها ، عليك أن توصل إلى بعض اللغة القياسية التي تصف الاستثناء المحدد الذي تقوم به للقواعد العادية.
لذلك أعتقد أن المعيار يجب أن يوضح المدمر الافتراضي ، أو حظره. إن جعل الأمر اختياريًا معقدًا للغاية (أو ربما يجب أن أقول ، أعتقد أنه سيتم الحكم عليه بشكل غير ضروري. التعقيد لم يوقف لجنة المعايير في المناطق التي تعتبر فيها جديرة بالاهتمام ...)
إذا تم حظره ، فيمكن أن يكون التنفيذ:
- إضافة مدمرة افتراضية إلى بعض فئة مشتقة من
type_info
- اشتق جميع كائنات typeinfo من الذي - التي صف دراسي
- استخدم ذلك داخليًا كطبقة قاعدة متعددة الأشكال لكل شيء
من شأنه أن يحل الموقف الذي وصفته في الجزء العلوي من المنشور ، لكن النوع الثابت من أ typeid
سيظل التعبير const std::type_info
, ، لذلك سيكون من الصعب على التطبيقات تحديد الإضافات حيث يمكن للبرامج dynamic_cast
إلى أهداف مختلفة لمعرفة أي نوع من type_info
كائن لديهم في حالة معينة. ربما كان المعيار يأمل في السماح بذلك ، على الرغم من أن التنفيذ يمكن أن يوفر دائمًا متغيرًا typeid
مع نوع ثابت مختلف ، أو ضمان أن أ static_cast
إلى فئة تمديد معينة ستعمل ، ثم اترك البرنامج dynamic_cast
من هناك.
باختصار ، بقدر ما أعرف أن المدمرة الافتراضية قد يكون مفيدًا للمنفذين ، وإزالته لا يكسب أي شخص آخر غير ذلك لن نقضي وقتًا نتساءل عن سبب وجوده ؛-)
*] في الواقع ، لم أظهر ذلك. لقد أثبتت أن برنامجًا غير قانوني ، كل شيء آخر متساوٍ ، يجمع. لكن ربما يمكن للتنفيذ أن ينجح في ذلك من خلال ضمان أن كل شيء غير متساوٍ ، وأنه لا يجمع. تعزيز is_polymorphic
ليس محمولًا ، لذلك على الرغم من أنه من الممكن لبرنامج اختبار هذا الفصل هو تعدد الأشكال ، يجب أن يكون ، قد يكون هناك أي وسيلة لبرنامج مطابق لاختبار هذا الفصل ليس تعدد الأشكال ، لا ينبغي أن يكون. على الرغم من أنه حتى لو كان من المستحيل ، فإن إثبات ذلك ، من أجل إزالة سطر واحد من المعيار ، هو الكثير من الجهد.
نصائح أخرى
يقول معيار C ++ ذلك typeid
إرجاع كائن من type_info ، أو فئة فرعية محددة من قبل التنفيذ. لذلك ... أعتقد أن هذا هو الجواب إلى حد كبير. لذلك لا أرى لماذا ترفض نقطتك 1 و 2.
الفقرة 5.2.8 الفقرة 1 من قياسي C ++ الحالي:
نتيجة التعبير النوعي هو lvalue من النوع الثابت const std :: type_info (18.5.1) والنوع الديناميكي const std :: type_info أو const حيث الاسم هو فئة محددة من التنفيذ مشتقة من std :: type_info التي تحافظ على السلوك الموصوف في 18.5.1.61) يمتد عمر الكائن المشار إليه بواسطة LVALUE إلى نهاية البرنامج. سواء تم استدعاء المدمر أم لا لكائن type_info في نهاية البرنامج غير محدد.
وهذا بدوره يعني أنه يمكن للمرء أن يكتب الرمز التالي قانوني وغرامة:const type_info& x = typeid(expr);
الذي قد يتطلب أن يكون type_info متعدد الأشكال
حول أبسط معرف "عالمي" يمكنك الحصول عليه في C ++ هو اسم الفصل ، و typeinfo
يوفر وسيلة لمقارنة هذا المعرف للمساواة. لكن التصميم محرج ومحدود لدرجة أنك تحتاج إلى الالتفاف بعد ذلك typeinfo
في بعض فئات الغلاف ، على سبيل المثال ، لتكون قادرًا على وضع مثيلات في المجموعات. فعل أندريه ألكساندرسكو ذلك في "تصميمه الحديث C ++" وأعتقد أن ذلك typeinfo
الغلاف جزء من مكتبة لوكي. من المحتمل أن يكون هناك واحد أيضًا في Boost ؛ ومن السهل جدًا لفك ، على سبيل المثال ، انظر غلاف بلدي.
ولكن حتى بالنسبة لمثل هذا الغلاف ، لا يوجد بشكل عام أي حاجة لمدمار افتراضي في typeinfo
.
وبالتالي ، فإن السؤال ليس "هاه ، لماذا يوجد مدمرة افتراضية" ، بل بالأحرى ، كما أراها ، "هاه ، لماذا التصميم متخلف ، محرج وغير قابل للاستخدام مباشرة"؟ وقد وضعت ذلك في عملية التقييس. على سبيل المثال ، لا تعد IoStreams أمثلة بالضبط على التصميم الرائع ، إما ؛ لا شيء لمحاكاة.
3/ يترك مفتوحة إمكانية metaclass مع أنواع مخصصة - كل متعدد الأشكال
class A
سوف تحصل على "metaclass" مشتقةA__type_info
, التي تستمد منtype_info
. ربما يمكن أن تعرض مثل هذه الفصول المشتقة الأعضاء تلك الاتصالnew A
مع مختلف حجج المنشئ بطريقة آمنة من النوع ، وأشياء من هذا القبيل. لكن صنعtype_info
تعدد الأشكال نفسه في الواقع يجعل مثل هذه الفكرة مستحيلة بشكل أساسي ، لأنه يجب أن يكون لديك metaclasses ل metaclasses ، adinitum ، وهذا يمثل مشكلة إذا كان كلtype_info
الكائنات لها مدة تخزين ثابت. ربما باستثناء هذا هو السبب لجعله متعدد الأشكال.
ماهر...
على أي حال ، أنا لا أتفق مع هذا المنطق: يمكن بسهولة استبعاد فئات التعريف للأنواع المستمدة من الأنواع المستمدة type_info
, ، بما فيها type_info
بحد ذاتها.