templates شرح - C++One std::vector يحتوي على فئة قالب لأنواع متعددة




example sort (5)

أحتاج إلى تخزين أنواع متعددة لفئة القالب في متجه واحد.

على سبيل المثال ، من أجل:

template <typename T>
class templateClass{
     bool someFunction();
};

أحتاج إلى ناقل واحد يخزن كل ما يلي:

templateClass<int> t1;
templateClass<char> t2;
templateClass<std::string> t3;
etc

بقدر ما أعرف هذا غير ممكن ، إذا كان يمكن للشخص أن يقول كيف؟

إذا لم يكن من الممكن أن يشرح أحدهم كيفية إجراء العمل التالي؟

كمحاولة ، حاولت استخدام فئة أساسية غير القالب وترث فئة القالب منه.

 class templateInterface{
     virtual bool someFunction() = 0;
 };

 template <typename T>
 class templateClass : public templateInterface{
     bool someFunction();
 };

ثم قمت بإنشاء متجه لتخزين الطبقة "templateInterface" قاعدة:

std::vector<templateInterface> v;
templateClass<int> t;
v.push_back(t);

هذا إنتاج الخطأ التالي:

error: cannot allocate an object of abstract type 'templateInterface'
note: because the following virtual functions are pure within 'templateInterface'
note: virtual bool templateInterface::someFunction()

لإصلاح هذا الخطأ ، قمت بعمل الوظيفة في templateInterface وليس الظاهري الظاهري عن طريق توفير هيئة وظيفة ، وهذا جمع ولكن عند استدعاء الدالة لم يتم استخدام overide ، ولكن بدلا من الجسم في وظيفة افتراضية.

على سبيل المثال:

 class templateInterface{
     virtual bool someFunction() {return true;}
 };

 template <typename T>
 class templateClass : public templateInterface{
     bool someFunction() {return false;}
 };

 std::vector<templateInterface> v;
 templateClass<int> i;
 v.push_back(i);
 v[0].someFunction(); //This returns true, and does not use the code in the 'templateClass' function body

هل هناك أي طريقة لإصلاح هذا بحيث يتم استخدام الدالة التي تم تجاوزها ، أو هل هناك حل بديل آخر لتخزين أنواع متعددة من القوالب في متجه واحد؟


Answers

لماذا لا يعمل الرمز الخاص بك:

إن استدعاء دالة ظاهرية على قيمة لا يستخدم تعدد الأشكال. تستدعي الدالة التي تم تعريفها لنوع هذا الرمز الدقيق كما يراه المترجم ، وليس نوع وقت التشغيل. عند إدراج أنواع فرعية في متجه من النوع الأساسي ، سيتم تحويل قيمك إلى النوع الأساسي ("type slicing") ، وهو ليس ما تريده. وستتصل وظائف الاتصال الآن بالوظيفة كما هي محددة للنوع الأساسي ، حيث إنها ليست من هذا النوع.

كيف يمكن اصلاح هذا؟

يمكن تكرار المشكلة نفسها باستخدام مقتطف الشفرة هذا:

templateInterface x = templateClass<int>(); // Type slicing takes place!
x.someFunction();  // -> templateInterface::someFunction() is called!

يعمل تعدد الأشكال فقط على مؤشر أو نوع مرجع . عندئذٍ ، سيستخدم نوع وقت التشغيل للكائن خلف المؤشر / المرجع لتحديد التنفيذ الذي سيتم الاتصال به (باستخدام vtable).

مؤشرات تحويل تماما "آمنة" فيما يتعلق بنوع تقطيع. لن يتم تحويل قيمك الفعلية على الإطلاق وسيعمل تعدد الأشكال كما هو متوقع.

مثال ، مماثل لمقتطف الشفرة أعلاه:

templateInterface *x = new templateClass<int>();  // No type slicing takes place
x->someFunction();  // -> templateClass<int>::someFunction() is called!

delete x;  // Don't forget to destroy your objects.

ماذا عن المتجهات؟

لذلك عليك أن تعتمد هذه التغييرات في التعليمات البرمجية الخاصة بك. يمكنك ببساطة تخزين المؤشرات على الأنواع الفعلية في المتجه ، بدلاً من تخزين القيم مباشرة.

عند التعامل مع المؤشرات ، عليك أيضًا الاهتمام بحذف الكائنات المخصصة الخاصة بك. لهذا ، يمكنك استخدام المؤشرات الذكية التي تهتم بالحذف تلقائيًا. unique_ptr هو أحد أنواع المؤشرات الذكية. فإنه يحذف المستأجر كلما يخرج من نطاق ("ملكية فريدة" - النطاق هو المالك). بافتراض أن عمر كائناتك مرتبط بالنطاق ، فهذا ما يجب عليك استخدامه:

std::vector<std::unique_ptr<templateInterface>> v;

templateClass<int> *i = new templateClass<int>();    // create new object
v.push_back(std::unique_ptr<templateInterface>(i));  // put it in the vector

v.emplace_back(new templateClass<int>());   // "direct" alternative

ثم ، استدعاء دالة ظاهرية على أحد هذه العناصر باستخدام بناء الجملة التالي:

v[0]->someFunction();

تأكد من جعل جميع الوظائف الافتراضية التي يمكن أن يكون من الممكن تجاوزها من قبل الفئات الفرعية. وإلا لن يتم استدعاء الإصدار المتجاوز. ولكن منذ أن قمت بالفعل بتقديم "واجهة" ، أنا متأكد من أنك تعمل مع وظائف مجردة.

النهج البديلة:

الطرق البديلة لفعل ما تريد هو استخدام نوع مختلف في المتجه. هناك بعض التطبيقات من أنواع مختلفة ، و Boost.Variant كونها شعبية جدا. هذا النهج لطيف بشكل خاص إذا لم يكن لديك تسلسل هرمي من نوع (على سبيل المثال عند تخزين أنواع بدائية). يمكنك عندئذ استخدام نوع متجه مثل std::vector<boost::variant<int, char, bool>>


تعدد الأشكال يعمل فقط من خلال المؤشرات أو المراجع. ستحتاج إلى قاعدة غير القالب. أبعد من ذلك ، ستحتاج إلى تحديد أين ستعيش الكائنات الفعلية في الحاوية. إذا كانت جميع الكائنات الثابتة (مع عمر كافٍ) ، فقط باستخدام std::vector<TemplateInterface*> ، وإدخالها مع v.push_back(&t1); ، وما إلى ذلك ، يجب أن تفعل الحيلة. بخلاف ذلك ، قد تحتاج إلى دعم الاستنساخ والحفاظ على استنساخ في المتجه: يفضل أن يكون مع حاويات مؤشر Boost ، ولكن يمكن استخدام std::shared_ptr كذلك.


إذا كنت تبحث في حاوية لتخزين أنواع متعددة ، فيجب عليك استكشاف Boost.Variant من مكتبة التحسين الشائعة.


الحلول التي تم تقديمها حتى الآن جيدة على الرغم من أنك تدرك أنه في حالة إرجاعك لنوع القالب غير منطقي في المثال الخاص بك ، فإن أيًا من هذه الأمور لن يساعدك لأن الفواصل الزمنية القابلة للقراءة لن تكون قابلة للقياس قبل اليد. هناك في الواقع حدود ، من وجهة نظر التصميم ، لاستخدام محلول متعدد الأشكال الموجه القالب.


يتم توثيق __func__ في المعيار C ++ 0x في القسم 8.4.1. في هذه الحالة ، يكون المتغير المحلي المحدد سابقًا للنموذج:

static const char __func__[] = "function-name ";

حيث "اسم الوظيفة" هو تنفيذ محدد. هذا يعني أنه كلما قمت بإعلان وظيفة ، سيضيف المحول البرمجي هذا المتغير ضمنيًا إلى وظيفتك. وينطبق الشيء نفسه على __FUNCTION__ و __PRETTY_FUNCTION__ . على الرغم من تعلقها ، فهي ليست وحدات الماكرو. على الرغم من أن __func__ عبارة عن إضافة إلى C ++ 0x

g++ -std=c++98 ....

سيظل تجميع التعليمات البرمجية باستخدام __func__ .

يتم توثيق __PRETTY_FUNCTION__ و __FUNCTION__ هنا http://gcc.gnu.org/onlinedocs/gcc-4.5.1/gcc/Function-Names.html#Function-Names . __FUNCTION__ هو مجرد اسم آخر لـ __func__ . __PRETTY_FUNCTION__ هو نفس __func__ في C ولكن في C ++ يحتوي على نوع التوقيع أيضًا.





c++ templates stdvector