سؤال

كنت أجيب سؤال قبل بضع دقائق ورفع لي واحدة أخرى:

في أحد مشاريعي ، أقوم ببعض تحليل رسائل الشبكة. الرسائل في شكل:

[1 byte message type][2 bytes payload length][x bytes payload]

يتم تحديد تنسيق ومحتوى الحمولة النافعة حسب نوع الرسالة. لديّ تسلسل هرمي فئة ، استنادًا إلى فئة مشتركة Message.

لتثبيت رسائلي ، لدي طريقة تحليل ثابتة تعيد أ Message* يعتمد على نوع الرسالة بايت. شيء مثل:

Message* parse(const char* frame)
{
  // This is sample code, in real life I obviously check that the buffer
  // is not NULL, and the size, and so on.

  switch(frame[0])
  {
    case 0x01:
      return new FooMessage();
    case 0x02:
      return new BarMessage();
  }

  // Throw an exception here because the mesage type is unknown.
}

أحتاج أحيانًا إلى الوصول إلى طرق الفئات الفرعية. منذ معالجة رسائل الشبكة الخاصة بي يجب كن سريعًا ، لقد عاشت لتجنب dynamic_cast<> وأضفت طريقة إلى القاعدة Message الفصل الذي يعيد نوع الرسالة. اعتمادًا على قيمة الإرجاع هذه ، أستخدم أ static_cast<> إلى الطفل الصحيح بدلاً من ذلك.

لقد فعلت هذا بشكل رئيسي لأنه قيل لي مرة واحدة dynamic_cast<> كان بطيئا. ومع ذلك ، لا أعرف بالضبط ماذا تفعل حقا وكم هو بطيئ ، وبالتالي ، قد تكون طريقتي بنفس القدر من البطيء (أو أبطأ) ولكن أكثر تعقيدًا.

ما رأيكم يا رفاق في هذا التصميم؟ هل هو شائع؟ هل هو أسرع حقا من استخدام dynamic_cast<> ؟ أي تفسير مفصل لما يحدث تحت الغطاء عند استخدام واحد dynamic_cast<> مرحب به !

--- تعديل ---

منذ أن سأل بعض الناس لماذا:

في الأساس ، عندما أتلقى إطارًا ، أفعل شيئين:

  1. أقوم بتحليل الرسالة وأبني مثيلًا مقابلًا من فئة فرعية من Message إذا كان محتوى الإطار صالحًا. هنالك رقم المنطق باستثناء جزء التحليل.
  2. أتلقى أ Message واعتمادًا على أ switch(message->getType()), ، أنا static_cast<> إلى النوع الصحيح وافعل كل ما يجب القيام به مع الرسالة.
هل كانت مفيدة؟

المحلول

سوف تختلف تطبيقات Dynamic_cast بالطبع حسب برنامج التحويل البرمجي.

في Visual C ++ ، يشير VTable إلى بنية تحتوي على جميع RTTI حول الهيكل. وبالتالي ، يتضمن Dynamic_cast إزالة هذا المؤشر ، والتحقق من النوع "الفعلي" مقابل النوع المطلوب ، ورمي استثناء (أو إرجاع فارغة) إذا لم تكن متوافقة. إنه يعادل بشكل أساسي النظام الذي تصفه. إنها ليست بطيئة.

يبدو تصميمك أيضًا بعيدًا قليلاً - لديك طريقة مصنع تنسى النوع الحقيقي للكائن ، ثم تريد على الفور إلغاء تكاليف هذه المعلومات. ربما يجب عليك نقل هذا المنطق الذي تقوم به عند عدم ترجمة نوع ما في طريقة المصنع ، أو إلى طرق افتراضية على الفئة الأساسية نفسها.

نصائح أخرى

الإجابة الصحيحة الوحيدة لـ "هل هي أسرع" هي "جربها".

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

هذا يعتمد على كيفية إدارة رسائلك. عندما يكون لدي switch لتحديد الرسالة بناءً على النوع ، فإن الخيار الأفضل هو الاستخدام static_cast, ، كما تعلمون أن محلل الوظيفة سوف يمنحك إنشاء النوع الصحيح.

Message* gmsg parse(frame);

switch (gmsg->type) {
  case FooMessage_type:
    FooMessage* msg=static_cast<FooMessage*)(gmsg);
    // ...
    break;
  case BarMessage_type:
    BarMessage* msg=static_cast<BarMessage*)(gmsg);
    //...
    break;      
};

استخدام dynamic_cast هنا هو الإنتاج المفرط.

لماذا تحتاج إلى أن جميع الرسائل ترث من واحدة مشتركة؟ ما هي القواسم المشتركة؟ سأضيف تصميمًا آخر لا يستخدم الميراث على الإطلاق

switch (frame::get_msg_type(aframe)) {
  case FooMessage_type:
    FooMessage msg=parse<FooMessage)(aframe);
    // work with msg
    break;
  case BarMessage_type:
    BarMessage msg=parse<BarMessage)(aframe);
    //...
    break;
};

حيث يقوم Parse بتوصيف إطار كـ MSG ، أو رمي استثناء عند فشل الحاجز.

أرى إجابة أخرى تخبرك باستخدام وظائف افتراضية. أنا حقًا لا أرى أي ميزة لتصميم OO لهذا الرسائل.

أ) هذا يشبه إلى حد كبير التحسين السابق لأوانه.

ب) إذا كان التصميم الخاص بك يتطلب الكثير من المكالمات إلى Dynamic_cast <> أنت قلق بشأنه ، فأنت بالتأكيد بحاجة إلى إلقاء نظرة على التصميم الخاص بك ومعرفة ما هو الخطأ في ذلك.

ج) كما قال الإجابة السابقة ، فإن الطريقة الوحيدة للإجابة على ما إذا كانت أسرع هي استخدام البروفيل (أو ما يعادلها) وإجراء مقارنة.

أنت تركز على السرعة ، ولكن ماذا عن الصواب؟

السؤال الأساسي هل أنت متأكد من أنك لن ترتكب خطأ؟ على وجه الخصوص ، قد تميل إلى لف طريقة الصب بهذه الطريقة:

template <class T>
T* convert(Message* message)
{
  if (message == 0) return 0;
  else return message->getType() == T::Type() ? static_cast<T*>(message) : 0;
}

من أجل تضمين الاختبار والتمثيل في وظيفة واحدة ، وبالتالي تجنب الخطأ مثل:

switch(message->getType())
{
case Foo:
{
  //...
  // fallthrough
}
case Bar:
{
  BarMessage* bar = static_cast<BarMessage*>(message); // got here through `Foo`
}
}

أو الواضح:

if (message->getType() == Foo) static_cast<BarMessage*>(message); // oups

ليس من المسلم به أن هذا ليس جهد كبير أيضًا.

من ناحية أخرى ، يمكنك أيضًا مراجعة تقنيتك عن طريق تطبيق إرسال وقت التشغيل.

  • virtual طُرق
  • Visitor

إلخ...

لديك بالفعل فئة قاعدة مجردة "رسالة". استخدمه كواجهة لإخفاء تفاصيل تنفيذ Foomessage و Barmessage.

أعتقد ، لهذا السبب اخترت هذا النهج ، أم أليس كذلك؟

أعلم أن هذا المنشور قديم بعض الشيء ، لكن كان لدي نفس المشكلة تمامًا مثل مؤلف هذا السؤال.

أحتاج أيضًا إلى إيقاف فئة قاعدة مجردة (MessageAbstract) إلى فئات الرسائل الملموسة واحدة أو أكثر في تبعية حقل النوع في أ MessageHeader. نظرًا لأن الرسائل الملموسة يمكن أن تختلف في طولها وبياناتها ، فلا يوجد إمكانية لتضمين كل شيء في MessageAbstract.

أنا أيضا استخدام static_cast النهج ، لأنني أكثر دراية بـ OOP أكثر من Metaprogramming.

باستخدام C ++ 11 في المشروع الحالي ، أردت تحديد الحل في هذه الإجابة. الحل التالي يشبه إلى حد كبير الحل الذي قدمه Vicente Botet Escriba ، ولكنه يستخدم الحديث C ++.

#include <cstdint>
#include <memory>

namespace Example {

enum class MessageTypes : std::uint8_t {
  kFooMessage = 0x01,
  kBarMessage = 0x02
};

class MessageHeader {
 public:
  explicit MessageHeader(MessageTypes const kType) : kType_{kType} {
  }

  MessageTypes type() const noexcept {
    return this->kType_;
  }

 private:
  MessageTypes const kType_;
};

class MessageAbstract {
 public:
  explicit MessageAbstract(MessageHeader const kHeader) : kHeader_{kHeader} {
  }

  MessageHeader header() const noexcept {
    return this->kHeader_;
  }

 private:
  MessageHeader const kHeader_;
};

class FooMessage : public MessageAbstract {
 public:
  void specific_method_for_class_foo_message() const noexcept {
  }
  // ...
};

class BarMessage : public MessageAbstract {
 public:
  void specific_method_for_class_bar_message() const noexcept {
  }
  // ...
};

using MessagePointer = std::shared_ptr<MessageAbstract const>;

}  // namespace Example

using namespace Example;

int main() {
  MessagePointer message_ptr{/* Creation Method / Factory Method */};

  switch (message_ptr->header().type()) {
    case MessageTypes::kFooMessage: {
      std::shared_ptr<FooMessage const> foo_message{std::static_pointer_cast<FooMessage const>(message_ptr)};
      foo_message->specific_method_for_class_foo_message();
      // ...
      break;
    }
    case MessageTypes::kBarMessage: {
      std::shared_ptr<BarMessage const> bar_message{std::static_pointer_cast<BarMessage const>(message_ptr)};
      bar_message->specific_method_for_class_bar_message();
      // ...
      break;
    }
    default:
      // Throw exception.
      break;
  }

  return 0;
}

لا أرى أي إجابات تلمس هذا ، لكن لا يمكنك إرسال كائنات C ++ عبر الشبكة وتتوقع أن تصل سليمة. يتم إعداد الجدول الافتراضي بناءً على حالة الذاكرة في الكمبيوتر المرسل ، فمن المحتمل جدًا أن يكون الكمبيوتر المستلم أشياء في نفس المكان. سيؤدي ذلك أيضًا إلى جعل RTTI فشل (وهو ما يستخدمه Dynamic_cast) نظرًا لأن RTTI غالبًا ما يتم تنفيذها مع VTABLE.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top