Мой метод для избежания dynamic_cast <> быстрее, чем сам dynamic_cast <>? - PullRequest
7 голосов
/ 03 мая 2010

Я отвечал на вопрос несколько минут назад, и он поднял мне еще один:

В одном из моих проектов я выполняю разбор сетевых сообщений. Сообщения имеют вид:

[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, если содержимое кадра допустимо. Существует логика no , за исключением части синтаксического анализа.
  2. Я получаю Message и в зависимости от switch(message->getType()), я static_cast<> для правильного типа и делаю все, что должно быть сделано с сообщением.

Ответы [ 9 ]

7 голосов
/ 03 мая 2010

Реализации dynamic_cast, конечно, будут отличаться в зависимости от компилятора.

В Visual C ++ vtable указывает на структуру, которая содержит все RTTI о структуре. Таким образом, dynamic_cast включает в себя разыменование этого указателя, проверку «фактического» типа по отношению к запрошенному типу и выдачу исключения (или возвращение NULL), если они несовместимы. Это в основном эквивалентно системе, которую вы описываете. Это не особенность медленная.

Ваш дизайн также звучит немного странно - у вас есть фабричный метод, который забывает истинный тип объекта, и вы сразу же хотите забыть эту информацию. Возможно, вам следует перенести ту логику, которую вы делаете, когда забываете тип, в метод фабрики или в виртуальные методы самого базового класса.

4 голосов
/ 03 мая 2010

Единственный правильный ответ на вопрос «быстрее» - «попробуй».

3 голосов
/ 03 мая 2010

Когда люди говорят, что dynamic_cast медленный, это просто правило. dynamic_cast более или менее делает то, что вы делаете. Это медленно, потому что включает в себя пару обращений к памяти. Вроде как, когда люди говорят, что виртуальные функции работают медленно. Вы берете что-то быстрое (вызов функции) и добавляете пару обращений к памяти. Это значительное замедление (так как весь путь до плунжера и обратно может занимать несколько сотен циклов), но для большинства людей это просто не имеет значения, если это делается не часто (с очень большим значением часто) .

3 голосов
/ 03 мая 2010

Это зависит от того, как вы управляете своими сообщениями.Когда у меня есть 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;
};

, где синтаксический анализ разбирает кадр как MSG, или выдает исключение при сбое синтаксического анализа.которые говорят вам использовать виртуальные функции.Я действительно не вижу никакого преимущества в этом дизайне ОО для сообщений.

2 голосов
/ 03 мая 2010

А) Это звучит очень похоже на преждевременную оптимизацию.

B) Если вашему дизайну требуется так много вызовов dynamic_cast <>, что вы о нем беспокоитесь, то вам определенно нужно взглянуть на свой дизайн и выяснить, что с ним не так.

C) Как и в предыдущем ответе, единственный способ ответить, быстрее ли это - использовать профилировщик (или его эквивалент) и провести сравнение.

1 голос
/ 03 мая 2010

Вы ориентируетесь на скорость, но как на правильность?

Основной вопрос: ты уверен, что не ошибешься? В частности, у вас может возникнуть желание обернуть метод литья таким образом:

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

и т.д ...

0 голосов
/ 17 апреля 2014

Я знаю, что этот пост немного стар, но у меня была точно такая же проблема, как и у автора этого вопроса.

Мне также нужно уменьшить абстрактный базовый класс (MessageAbstract) до одного или нескольких конкретных классов сообщений в зависимости от поля типа в MessageHeader. Поскольку конкретные сообщения могут различаться по своей длине и данным, нет возможности включить все в MessageAbstract.

Я также использую подход static_cast, так как я больше знаком с ООП, чем с метапрограммированием.

Используя C ++ 11 в текущем проекте, я хотел изложить свое решение в этом ответе. Следующее решение очень похоже на решение, предоставленное Vicente Botet Ecriba, но использует Modern 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;
}
0 голосов
/ 04 мая 2010

Я не вижу никаких ответов на этот вопрос, но вы не можете отправлять объекты C ++ по сети и ожидать, что они поступят в целости и сохранности. Виртуальная таблица настраивается на основе состояния памяти отправляющего компьютера, вполне вероятно, что на принимающем компьютере не будет вещей в том же месте. Это также обычно приводит к сбою RTTI (именно это использует dynamic_cast), поскольку RTTI часто реализуется вместе с vtable.

0 голосов
/ 03 мая 2010

У вас уже есть абстрактный базовый класс «Сообщение». Используйте его как интерфейс, чтобы скрыть детали реализации FooMessage и BarMessage.

Полагаю, именно поэтому вы выбрали такой подход или нет?

...