Question

Je répondais un question il y a quelques minutes et il a soulevé pour me l'autre:

Dans un de mes projets, je fais un peu de messages de l'analyse réseau. Les messages sont sous la forme de:

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

Le format et le contenu de la charge utile sont déterminées par le type de message. J'ai une hiérarchie de classes, basée sur une Message de classe commune.

Pour instancier mes messages, j'ai une méthode d'analyse de statique qui rend un Message* en fonction de l'octet de type message . Quelque chose comme:

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.
}

Je dois parfois d'accéder aux méthodes des sous-classes. Depuis mon traitement des messages réseau doit être rapide, je decived pour éviter dynamic_cast<> et j'ai ajouté une méthode à la classe de base Message qui rend le type de message. En fonction de cette valeur de retour, j'utilise un static_cast<> au type de droit de l'enfant à la place.

Je l'ai fait surtout parce qu'on m'a dit une fois que dynamic_cast<> était lent. Cependant, je ne sais pas exactement ce qu'il fait vraiment et la lenteur est, donc, ma méthode peut être aussi tout aussi lent (ou moins), mais beaucoup plus compliqué.

Qu'est-ce que vous pensez les gars de cette conception? Est-il courant? Est-il vraiment plus rapide que d'utiliser dynamic_cast<>? Toute explication détaillée de ce qui se passe sous le capot quand on dynamic_cast<> utilisation est la bienvenue!

--- EDIT ---

Étant donné que certaines personnes ont demandé pourquoi:

En fait, quand je reçois un cadre, je fais deux choses:

  1. Je parser le message et construire une instance correspondante d'une sous-classe de Message si le contenu du cadre est valide. Il est pas logique sauf pour la partie de l'analyse syntaxique.
  2. Je reçois un Message et selon une switch(message->getType()), je static_cast<> au type et faire tout ce qui doit être fait avec le message.
Était-ce utile?

La solution

Implémentations de dynamic_cast sera de varier bien sûr par le compilateur.

Dans Visual C ++, les points vtable à une structure qui contient toutes les RTTI sur une structure. Un dynamic_cast implique donc déréférencer ce pointeur, et la vérification du type « réelle » contre le type demandé, et lancer une exception (ou retour NULL) si elles ne sont pas compatibles. Il est essentiellement équivalent au système que vous décrivez. Ce n'est pas particularité lente.

Votre conception semble aussi un peu hors - vous avez une méthode de fabrication qui oublie le vrai type d'un objet, alors vous voulez immédiatement non oublier que l'information. Peut-être que vous devez déplacer cette logique que vous faites quand unforgetting un type dans la méthode de fabrication ou dans des méthodes virtuelles sur la classe de base elle-même.

Autres conseils

La seule réponse correcte à « est plus rapide » est « l'essayer. »

Quand les gens disent dynamic_cast est lent, il est juste une règle de base. dynamic_cast plus ou moins fait ce que vous faites. Il est lent, car il implique un couple d'accès mémoire. Un peu comme quand les gens disent que des fonctions virtuelles sont lentes. Vous prenez quelque chose de rapide (un appel de fonction) et l'ajout d'un couple d'accès mémoire à elle. Il est un ralentissement significatif (puisque tout le chemin à la RAM et le dos peut prendre quelques centaines de cycles), mais pour la plupart des gens, il n'a pas d'importance tant que cela ne se fait pas souvent (avec une valeur très grande souvent) .

Cela dépend de la façon dont vous gérez vos messages. Quand j'ai un switch pour sélectionner le message en fonction du type la meilleure option est d'utiliser static_cast, comme vous le savez que l'analyseur de fonction vous donnera la créer le type correct.

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;      
};

L'utilisation de dynamic_cast ici est surprotéger.

Pourquoi avez-vous besoin que tous les messages hérite d'un commun un? Quels sont les points communs? Je vais ajouter une autre conception qui n'utilise pas l'héritage du tout

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;
};

où parse analyse une trame en tant que MSG, ou lever une exception lorsque l'analyse échoue.

Je vois d'autre réponse qui vous dites d'utiliser des fonctions virtuelles. Je ne vois vraiment aucun avantage à cette conception orientée objet pour les messages.

A) Cela ressemble fort comme l'optimisation prématurée.

B) Si votre conception nécessite tant d'appels à dynamic_cast <> que vous êtes inquiet à ce sujet, alors vous avez certainement besoin de jeter un oeil à votre conception et comprendre ce qui ne va pas avec elle.

C) Comme la réponse précédente a dit, la seule façon de répondre que ce soit plus rapide est d'utiliser un profileur (ou équivalent) et faire une comparaison.

Vous l'accent sur la vitesse, mais que de l'exactitude?

La question sous-jacente est Etes-vous sûr ne fait une erreur? En particulier, vous pourriez être tenté d'envelopper le procédé de coulée de manière d'un tel:

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

Afin de pouvoir incorporer le test et la distribution d'une fonction unique, ce qui évite erreur tel que:

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

ou l'évidence:

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

Pas grand-chose certes, mais pas beaucoup d'efforts non plus.

D'autre part, vous pouvez également consulter votre Technic en appliquant l'envoi d'exécution.

  • méthodes de virtual
  • Visitor

etc ...

Vous avez déjà une classe de base abstraite « Message ». Utilisez-le comme une interface pour cacher les détails de mise en œuvre de FooMessage et BarMessage.

Je suppose que, c'est pourquoi vous avez choisi cette approche, ou non?

Je sais que ce post est un peu vieux, mais j'avais exactement le même problème que l'auteur de cette question.

Je dois aussi downcaster une classe de base abstraite (de MessageAbstract) à une ou plusieurs classes de messages concrets dans la dépendance d'un champ de type dans un MessageHeader. Comme les messages de béton peuvent différer dans leur longueur et leurs données, il n'y a pas possibilité d'inclure tout MessageAbstract.

J'utilise aussi l'approche static_cast, puisque je suis plus familier avec la POO qu'avec métaprogrammation.

Utilisation de C ++ 11 dans le projet actuel, je voulais décrire ma solution dans cette réponse. La solution suivante est très similaire à celui fourni par Vicente Botet Escriba, mais utilise moderne 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;
}

Je ne vois pas de réponses touchant à ce sujet, mais vous ne pouvez pas envoyer des objets de C sur le réseau et attendre qu'ils arrivent intacts. Le tableau virtuel est mis en place en fonction de l'état de la mémoire dans l'ordinateur d'envoi, il est fort probable que l'ordinateur réception aura pas de choses au même endroit. Cela permettra également généralement faire RTTI fail (qui est ce que dynamic_cast utilisations) depuis RTTI est souvent mis en œuvre avec le vtable.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top