2010-11-11 43 views
2

Supposons la classe de jouets suivante, et les compilateurs modernes (gcc récent par exemple).C++ performance de la primitive de retour par valeur ou par référence (const)

template <typename T> 
class SomeVec { 
public: 
    ... 
    virtual T get(const int index) = 0; 
} 

L'application implique une bonne quantité de nombre crissement en fonction des valeurs stockées dans les sous-classes SomeVec, T étant un type primitif, dire int. Toutefois, la pratique stl containers et boost::numeric::ublas::vector consiste à renvoyer des valeurs stockées via (const) reference.

Je me suis demandé quelles différences de performance cela impliquait. Dans this question, il est montré que l'accès aux éléments de tableau par la valeur et l'accès aux éléments vectoriels par référence résulte dans le même code, donc je suppose que le compilateur peut dans certains cas optimiser les choses.

Maintenant, mes questions sont les suivantes:

  • (1) stl et ublas sont basés sur des modèles, alors que ma solution nécessite des méthodes virtuelles. Est-ce que cela entrave la capacité des compilateurs modernes à optimiser le code?

  • (2) Si le compilateur ne pouvait pas optimiser pour renvoyer la référence atomique const comme valeur, puis-je supposer que l'appel de la méthode virtuelle et le déréférencement coûtent approximativement le même? Ou est-ce que l'un est significativement plus cher que l'autre?

Merci!

+0

Vous avez ici une méthode purement virtuelle, ce qui implique que vous avez des classes dérivées. Pouvez-vous donner un exemple? –

+0

@Oli: Les sous-classes peuvent être diverses, mais par exemple une implémentation simple aurait un vecteur interne ou une carte et retournerait l'une des valeurs stockées. Bien sûr, cela implique beaucoup plus de frais généraux que le type de retour impose, mais ne considérons pas cela. – ron

Répondre

3

La raison pour laquelle STL renvoie des références est parce que le code basé sur un modèle n'a pas le luxe de savoir que les objets retournés sont petits. Alors qu'un int ne pose aucun problème, retourner une grande structure ralentit les choses sans raison valable. Dans ce dernier cas, il est logique d'utiliser des références, et comme un compilateur raisonnable peut optimiser l'utilisation de types primitifs, vous pouvez aussi bien utiliser des références. Notez que votre méthode virtual T get(const int index) diffère par d'autres façons des méthodes de conteneur STL. Plus important encore, et lié au problème ci-dessus, votre méthode renvoie une copie de l'objet indexé: la manipulation du résultat ne changera pas l'état de l'objet dans votre conteneur. De même, déclarer que l'argument d'index est const ne fait rien, puisque vous passez index en valeur et que tout ce que vous faites est de vous empêcher de changer localement index dans l'implémentation. Si vous transmettiez index par référence, ce serait différent, mais vous devriez le faire en be wary. Enfin, êtes-vous vraiment sûr que votre classe doit être dynamiquement polymorphe (c.-à-d., avez des méthodes virtuelles)? Les conteneurs STL sont intentionnellement conçus pour ne pas être hérités (ce qui explique pourquoi ils n'ont pas de destructeurs virtual). Les conteneurs ne sont pas destinés à fournir une interface pour les classes dérivées, ils sont plutôt là pour faciliter une implémentation. Je dirais que les exemples de sous-classes que vous suggérez pourraient tout aussi bien être implémentés comme classes wrapper autour d'un conteneur structuré, favorisant la réutilisation du code par la composition sur l'héritage (quelque chose préconisé par le Gang of Four, entre autres). En plus d'être une bonne pratique, éviter les méthodes permet d'enregistrer des vtables et des pointeurs correspondants dans vos objets, et nécessite la recherche de vtable supplémentaire dans chaque appel. Si vous n'avez pas vraiment besoin de polymorphisme dynamique, pourquoi prendre le coût (et éventuellement empêcher les optimisations du compilateur)?

2

Il est inhabituel d'avoir une fonction virtuelle dans une classe de modèle. Si la fonction n'est pas virtuelle, le compilateur alignera généralement le code et optimisera la différence entre un retour par référence et un retour par valeur à zéro.

Le compilateur peut toujours intégrer une fonction si elle n'est pas appelée via un pointeur ou une référence - le compilateur connaîtra la fonction membre exacte à appeler dans ce cas, et n'a pas besoin de la rechercher dans un vtable .

Le coût d'avoir une référence sera petit, juste un seul déréférencement. Ce n'est peut-être même pas une instruction complète au niveau de l'assemblée.

+0

Je vois, merci. Dans mon cas, la recherche vtable sera requise. Comme pour template + virtual, le template est requis pour différents types contenus, tandis que l'abstraction via virtual est dû au fait que les méthodes de récupération de l'élément container peuvent être variées et doivent être cachées à l'utilisateur. – ron