2010-06-09 9 views
2

m'a donné le code suivant:la surcharge des opérateurs et l'héritage

class FibHeapNode 
{ 
    //... 

    // These all have trivial implementation 
    virtual void operator =(FibHeapNode& RHS); 
    virtual int operator ==(FibHeapNode& RHS); 
    virtual int operator <(FibHeapNode& RHS); 

}; 

class Event : public FibHeapNode 
{ 
    // These have nontrivial implementation 
    virtual void operator=(FibHeapNode& RHS); 
    virtual int operator==(FibHeapNode& RHS); 
    virtual int operator<(FibHeapNode& RHS); 

}; 

class FibHeap 
{ 
    //... 

    int DecreaseKey(FibHeapNode *theNode, FibHeapNode& NewKey) 
    { 
      FibHeapNode *theParent; 

      // Some code 

      if (theParent != NULL && *theNode < *theParent) 
      { 
      //... 
      } 

      //... 
      return 1; 
    } 
}; 

Une grande partie de la mise en œuvre de FibHeap est similaire: pointeurs de FibHeapNode sont déréférencé puis comparés.

Pourquoi ce code fonctionne-t-il? (ou est-ce buggé?) Je penserais que le virtual ici n'aurait aucun effet: puisque * theNode et * theParent ne sont pas des types de pointeur ou de référence, aucun dispatch dynamique ne se produit et FibHeapNode :: opérateur < est appelé n'importe ce qui est écrit dans l'événement.

Répondre

5

Vous devez être un peu confus au sujet de la répartition dynamique.

On dit souvent que "l'envoi dynamique se produit uniquement lorsque vous passez un appel via un pointeur ou via une référence". Formellement parlant, cette déclaration est totalement faux et trompeuse.

envoi dynamique en C++ se toujours lorsque vous appelez une fonction virtuelle , avec une et une seule exception: l'envoi dynamique est désactivée lorsque vous utilisez un nom pleinement qualifié de la fonction cible. Par exemple:

some_pointer->SomeClass::some_function(); // fully-qualified name 

Dans le code ci-dessus l'appel sera envoyé statiquement, même si some_function est une fonction virtuelle. Du point de vue du langage, il y a non d'autres moyens pour éviter la répartition dynamique, c'est-à-dire dans tous les autres cas tous les appels aux fonctions virtuelles sont envoyés dynamiquement. Ce que vous utilisez: un pointeur, une référence, un objet immédiat - n'a pas d'importance, l'envoi est toujours dynamique. Où vous appelez la fonction de: de constructeur/destructeur ou d'ailleurs - peu importe, l'envoi est toujours dynamique. Et je le répète: c'est ainsi que les choses se passent du point de vue du langage C++ lui-même. C'est ainsi qu'une "machine C++ abstraite" fonctionne. Ce qui se passe en pratique cependant, c'est que dans de nombreux cas, la répartition dynamique peut être remplacée par une répartition statique, car le compilateur connaît le type dynamique de l'objet à l'avance, au moment de la compilation et, par conséquent, connaît la cible de l'expédition. Dans de tels cas, il est plus logique d'appeler la fonction cible directement, au lieu de passer par le mécanisme de répartition dynamique plus coûteux. Pourtant, ce n'est rien d'autre qu'une optimisation. Certaines personnes, malheureusement, confondent cette optimisation pour un comportement exigé par la langue, en arrivant avec des instructions sans signification telles que "l'envoi dynamique se produit uniquement lorsque vous faites un appel à travers un pointeur ou à travers une référence".

Dans votre cas spécifique, l'envoi est dynamique. Puisque dans votre cas le compilateur ne connaît pas les types dynamiques des objets impliqués, il ne peut pas l'optimiser dans une répartition statique, c'est pourquoi votre code "fonctionne comme prévu".

P.S. Anticiper les questions possibles sur ce que j'ai dit plus haut: La répartition dynamique des appels faits à partir de constructeurs/destructeurs est limitée par le type dynamique actuel de l'objet, ce qui explique pourquoi il est possible (et facile) pour le compilateur de les optimiser. une répartition statique. C'est la raison d'une autre légende urbaine populaire qui stipule que les appels virtuels des constructeurs/destructeurs sont résolus statiquement.En réalité, en général, ils sont résolus dynamiquement, comme ils le devraient (encore une fois, en observant le type dynamique actuel de l'objet).

+2

Vous dites que même si vous appelez une méthode via un objet immédiat, l'adresse de la fonction est résolue lors de l'exécution? Cela ne semble pas probable, étant donné que cela serait inutilement inefficace. Qu'est-ce que je rate? –

+0

@Oli Charlesworth: Non, ce que je dis, c'est que du point de vue du langage C++, même avec un objet immédiat, l'appel est résolu en fonction du type * dynamic * de l'objet, c'est-à-dire * dynamique * . Cependant, avec l'objet immédiat, le type dynamique est connu au moment de la compilation, donc, comme je l'ai dit plus haut, la plupart des compilateurs remplaceront l'appel dynamique par un appel statique. C'est ce que vous observez en pratique. Dans ce cas, l'appel statique n'est pas une fonctionnalité C++, mais une simple optimisation spécifique au compilateur. – AnT

+0

@AndreyT: au risque de s'enfoncer dans la sémantique, n'est-il pas inutile de parler du type dynamique d'une variable liée à l'avance? La sémantique de 'T a; a.foo(); 'sont identiques si' foo' est une fonction virtuelle ou pas, alors qu'est-ce que cela signifie de classer cela comme 'dispatch dynamique'? –

1

* theNode et * theParent sont des types de référence. En fait, FibHeapNode & ref est très équivalent à * theNode.

+1

Les expressions en C++ * jamais * ont un type de référence. Chaque fois que vous travaillez avec une référence de type 'T &' dans un contexte d'expression, elle est immédiatement interprétée comme une lvalue de type 'T'. Le résultat de l'opérateur unaire '*' est * jamais * une référence. Le résultat est toujours un * lvalue *, ce qui est correct, mais l'appeler "une référence" est totalement incorrect. – AnT

+0

D'accord avec AndreyT: c'est une * lvalue * et non une référence, mais être imprécis dans ce cas particulier a l'avantage de simplifier la vie et C++ de comprendre :) (Le type d'application de l'opérateur de déréférencement à un pointeur de type 'T *' est une * lvalue * de type 'T', pas un' T & '§5.3.1/1) –

+0

' int * i = nouveau int (0); * i = 1; std :: cout << * i; 'Strange, il sort 1, presque comme si * i était une référence à l'int. – Puppy