تحتاج إلى توضيحات في CATS على غرار C ، وإعادة تفسير ، و const
-
23-09-2019 - |
سؤال
هل أنا على حق في افتراض أن الممثلين على الطراز C (التي يتم تثبيتها) ليست سوى إعادة الانقسام؟ إن استخدام هذا الأخير لافت للنظر بصريًا وسهل البحث عند البحث عن قوالب سيئة ، وبالتالي يوصى به على القبائل على غرار C؟
إذا تم إلقاء const باستخدام const_cast والكتابة إلى كائن const في الأصل غير محدد ، ما هو الغرض من const_cast؟
ملحوظة: أعلم أن Bjarne يدين بحق العمليات بأنها غير آمنة وحتى يذهب إلى حد الإبلاغ " البشع يجب أن يكون للعملية نموذجًا نحليًا قبيحًا. "ومن هنا جاءت صرف مشغلي الصب في C ++. لذلك سأحاول تقليل استخدامهم. الوعد. :)
المحلول
لا. const_cast
, ، أ static_cast
, ، أ reinterpret_cast
, ، أو مزيج منها. في حال لم يكن ذلك كافيًا ، يمكن أن يفعل أيضًا خدعة بسيطة واحدة على الأقل لا مزيج من الأحدث يمكن أن تفعل على الإطلاق!
يمكنك استخدام const_cast
مع النتائج المحددة إذا تم تعريف المتغير الأصلي بدون const
, ، ولكن كل ما لديك هو const
مؤشر أو إشارة إلى هذا الكائن. otoh ، إذا كنت تعتقد أن لديك سبب وجيه لاستخدام أ const_cast
, ، هناك احتمالات أنه يجب عليك البحث حقًا mutable
في حين أن.
تحرير: أفترض أنني يجب أن أقول ذلك فورًا ، ولكن يمكن أن يتحول فريق C-tyle على طراز C فئة قاعدة لا يمكن الوصول إليها. على سبيل المثال ، فكر في شيء مثل:
تحرير: أقوم بتحديث الكود إلى شيء سيتم تجميعه و (عادة) يوضح المشكلة.
#include <iostream>
class base1 {
public:
virtual void print() { std::cout << "base 1\n"; }
};
class base2 {
public:
virtual void print() { std::cout << "base 2\n"; }
};
class derived : base1, base2 {}; // note: private inheritance
int main() {
derived *d = new derived;
base1 *b1 = (base1 *)d; // allowed
b1->print(); // prints "base 1"
base2 *b2 = (base2 *)d; // also allowed
b2->print(); // prints "base 2"
// base1 *bb1 = static_cast<base *>(d); // not allowed: base is inaccessible
// Using `reinterpret_cast` allows the code to compile.
// Unfortunately the result is different, and normally won't work.
base1 *bb2 = reinterpret_cast<base1 *>(d);
bb2->print(); // may cause nasal demons.
base2 *bb3 = reinterpret_cast<base2 *>(d);
bb3->print(); // likewise
return 0;
}
الرمز باستخدام reinterpret_cast
سوف تجمع S - ولكن محاولة استخدام النتيجة (من خشية واحدة من الاثنين) سوف تسبب مشكلة كبيرة. ال reinterpret_cast
يأخذ يتمركز عنوان الكائن المشتق ومحاولات معاملته كما لو كان النوع المحدد من الكائن الأساسي - وبما أن (على الأكثر) يمكن أن يوجد كائن أساسي واحد في هذا العنوان ، في محاولة لمعاملته كآخرين يمكن أن يسبب/سيؤدي إلى تخصص مشاكل. تحرير: في هذه الحالة ، تكون الفصول متطابقة بشكل أساسي باستثناء ما تطبعها ، لذلك على الرغم من أي شيء يستطع يحدث ، مع معظم المجمعين ، سيقوم كل من الأخيرين بطباعة "قاعدة 1". يأخذ REINTERPRET_CAST كل ما يحدث في هذا العنوان ويحاول استخدامه كنوع محدد. في هذه الحالة ، لقد حاولت (حاول) أن أفعل شيئًا غير ضار ولكن مرئي. في الكود الحقيقي ، ربما لن تكون النتيجة جميلة جدًا.
سيعمل طاقم الممثلين على طراز C مثل static_cast إذا كان الكود قد استخدم الميراث العام بدلاً من القطاع الخاص-أي أنه يدرك مكان كل كائن من فئة الأساس المشتقة ، وضبط النتيجة ، لذلك سيقوم كل مؤشر ناتج العمل لأنه تم ضبطه لتشير في المكان المناسب.
نصائح أخرى
لا ، يمكن أن تعمل الممثلين على غرار C كما reinterpret_cast
س، const-cast
S أو static_cast
ق اعتمادا على الموقف. هذا هو السبب في إحباطهم - ترى ممثلين على غرار C في التعليمات البرمجية وتحتاج إلى البحث عن تفاصيل لمعرفة ما ستفعله. علي سبيل المثال:
const char* source;
int* target = (int*)source;// - acts as const_cast and reinterpret_cast at once
//int* target = retinterpret_cast<int*>source;// - won't compile - can't remove const
تذكر أن فريق Const قد يتصرف على شيء آخر ثم المعرف الأصلي:
void doit(const std::string &cs)
{
std::string &ms = const_cast<std::string &>(cs);
}
int main()
{
std::string s;
doit(s);
}
لذا ، بينما تقوم Doit بإلقاء Const ، في هذا المثال ، فإن السلسلة الأساسية ليست const ، لذا لا يوجد سلوك غير محدد.
تحديث
حسنًا ، إليك مثال أفضل عند استخدام Const_cast لا قيمة له تمامًا. نبدأ بفئة قاعدة مع وظيفة افتراضية تأخذ معلمة const:
class base
{
public:
virtual void doit(const std::string &str);
};
والآن تريد تجاوز هذه الوظيفة الافتراضية.
class child : public base
{
public:
virtual void doit(const std::string &str)
{
std::string &mstr = const_cast<std::string &>(str);
}
};
بسبب منطق/بنية الكود الخاص بك ، أنت تعرف ذلك child::doit
سيتم استدعاؤه فقط مع السلاسل غير الممتدة (و class base
ليس تحت سيطرتك ، لذا لا يمكنك تعديله ولا يمكنك تغيير توقيعه child::doit
لأنه بعد ذلك لن يتجاوز base::doit
). في هذه الحالة ، من الآمن أن نلقي بعيدا.
نعم ، هذا محفوف بالمخاطر. ربما عندما تكتب ذلك ، صحيح أن التنفيذ لن يصل أبدًا child::doit
مع سلسلة غير مخصصة والرمز صالح. ولكن قد يتغير ذلك إما أثناء الحفاظ على برنامجك أو ربما عند إعادة البناء والتقاط أحدث إصدار من class base
.
const_cast
يستخدم لإزالة const
من نوع. يمكنه أيضًا إزالته volatile
. إذا كان الكائن حقًا const
ثم لا يمكن كتابة النتيجة ولا تزال سلوكًا محددًا جيدًا. ومع ذلك ، إذا تم الترويج لها const
(من خلال تمريره إلى أ const T
وظيفة ، ثم const_cast
جي العودة إلى غيرconst
على ما يرام. (لقد وجدت المزيد من المعلومات هنا)
reinterpret_cast
لا تستطيع إزالة const
أو volatile
من نوع.
إن الممثلين على الطراز C هي في الحقيقة مطرقة للبرمجة - أنت تخبر المترجم بشكل أساسي أن ربط المربعة هناك سوف يناسب هذه الثقب المستدير بغض النظر عن ما. في هذا المعنى، reinterpret_cast
متشابه كثيرا.
الميزة الرئيسية التي أراها في استخدام مشغلي الممثلين C ++-النمط هي أنها تسمح لك بالتعبير عن نيتك بشكل أفضل والسماح للمترجم بالاستمرار -كل النمط C يلقي.
متعلق const_cast
- غالبًا ما تدخل في الموقف الذي تقوم فيه بتمرير كائن من خلال مرجع const ببساطة لأن واجهة برمجة التطبيقات تتطلب منك القيام بذلك. قل ، لقد حصلت على وظيفة x التي تعود إلى سلسلة على غرار C:
void X(const char *str) { ... }
داخل هذه الوظيفة ، تقوم بتمرير المعلمة إلى دالة C التي تتوقع char *
, ، على الرغم من أنها لا تغير السلسلة. الطريقة الوحيدة لاستيعاب هذا ستكون const_cast
شارع.
سأكون حريصًا جدًا على استخدام أي نوع من الممثلين ، وغالبًا ما يظهر هذا أن هناك شيئًا غير صحيح تمامًا في التصميم الخاص بك ، ولكن في بعض الأحيان يتعين عليك إقناع المترجم بأن الوتد الذي يبحث عنه ليس مربعًا كما يفترض. عندها فقط يجب أن تستخدم مشغلي الممثلين.