Classe abstraite, paramètre abstrait et héritage

a marqué ce sujet comme résolu.

Salut,

J’ai rejoint un projet de recherche qui utilise OOQP, en particulier sa classe OoqpVector. C’est une classe abstraite avec un certain nombre de méthodes virtuelles, par exemple :

virtual double dotProductWith(OoqpVector& v) = 0

Cette méthod est implémentée par deux sous-classes SimpleVector et DistributedVector. En raison de la généricité, OoqpVector::dotProductWith autorise toutes les combinaisons possibles :

  • SimpleVector.dotProductWith(SimpleVector)
  • DistributedVector.dotProductWith(DistributedVector)
  • SimpleVector.dotProductWith(DistributedVector)
  • DistributedVector.dotProductWith(SimpleVector)

Par contre, en pratique, la méthode est toujours appelée sur 2 vecteurs de même type (combinaisons 1 et 2). Les auteurs de OOQP ont donc implémenté SimpleVector::dotProductWith(OoqpVector&) et DistributedVector::dotProductWith(OoqpVector&) en castant le paramètre vers le type courant (Simple ou Distributed).

Ca fonctionne sans problème, mais je trouve ça assez vilain et je me demande s’il est possible de se passer des casts.

Une possibilité serait de virer OoqpVector::dotProductWith, de déclarer les méthodes spécialisées SimpleVector::dotProductWith(SimpleVector&) et DistributedVector::dotProductWith(DistributedVector&), et d’imposer aux classes/méthodes appelantes à forcer le même type pour les 2 vecteurs. Cela dit, le code utilise pas mal de OoqpVectors (abstraits) un peu partout, donc ça m’a l’air compliqué.

Auriez-vous des idées ?
Merci,

Charlie

La première idée qui me vient est de demander s’il y a une raison d’utiliser une lib qui n’a pas bougé en 7 ans et qui n’est pas une des célébrités du domaine (eigen, blaze, armadillo).

L’abus d’objets de la sorte (et de divers antipatterns que je vois comme mélanger fabs avec des doubles) me laisse un sentiment de chercheur non spécialiste du calcul HPC et qui découvre le C++ tandis qu’il transpose ses connaissances du Java et travaille en parallèle sur sa thèse. J’imagine que la lib a le calcul dont vous avez absolument besoin et qui ne se trouve pas ailleurs?

Il y a quelque chose que je ne comprends pas. Comment veux-tu te passer des casts alors qu’ils engrainés dans la bibliothèque, ou encore virer des fonctions? Tu as la main sur la lib pour la modifier?

Il faut libérer les fonctions pour moi vu ce coté: en vrai on ne mélange pas. Donc j’irai même plus loin si on ne mélange pas, est-ce vraiment des sous-types substituables que l’on voulait écrire? Mais es-tu prêt pour porter une révolution de dés-OO-fication de la lib pour passer au polymorphisme paramétrique?

Merci pour ta réponse.

Effectivement, on a la main sur le code et on ne s’est pas gênés pour le triturer et en virer une bonne partie. La bibli est typique d’un code d’il y a 20 ans et je m’en passerais bien.

La classe Vector (et ses sous-classes) est utilisée intensivement dans notre code, mais je suis prêt à développer une nouvelle version si celle-ci est plus simple à maintenir. J’ai effectivement pensé à une alternative qui consiste à paramétriser les classes appelantes par un sous-type V de Vector (donc SimpleVector ou DistributedVector) afin de forcer le même type pour tous les vecteurs :

template <type V>
class NomClasseAppelante {
  double methode() {
    V vector1 = ...
    V vector2 = ...
    double v1_dot_v2 = vector1.dot(vector2);
    ...
  }
}

puis d’instantier V à SimpleVector ou DistributedVector. C’est à cette méthode que tu pensais ?

Mon collègue/co-développeur approuve globalement la méthode mais a peur qu’il faille paramétrer beaucoup de nos classes. A voir…

Quant à la pertinence des sous-types substituables : bonne question. L’idée dans notre code est de pouvoir switcher entre 3 modes : calcul séquentiel, calcul parallèle et calcul distribué. Pour chaque mode, on a une sous-classe de Vector qui travaille avec une certaine représentation. L’avantage de la classe abstraite Vector est que les classes appelantes n’ont pas besoin de savoir dans quel mode on se trouve. Mais comme on l’a vu plus haut, on doit pouvoir régler ça via polymorphisme paramétrique.

+0 -0

Votre code. Savez-vous quel type de vecteur il est censé utiliser à un instant T? Si oui, utilisez ce type là. Si vous arrivez à bannir l’emploi du supertype, bingo vous pourrez virer ce pseudo polymorphisme non LSPien à mon goût (vu qu’il refuse la mixité)

Et pour les appels sans cast, il faut libérer les fonctions. ie.

void votre_code() {
    using V = SimpleVector ; // à supposer que cela soit possible
    V v1 = ...
    V v2 = ...
    auto ps = dot(v1, v2); // libération de la fonction: elle n'est plus membre

Ce que je propose c’est qu’au lieu d’avoir SimpleVector::dot(ParentVector cont&) const qui caste le paramètre en SimpleVector et même chose pour les distribués, il n’y a maintenant plus qu’une fonction qui prend 2 SimpleVector, et une autre pour les distribués (d’ailleurs il sort d’où cet autre enfant?).

Mais ceci ne sera possible que si votre code manipule explicitement un type enfant ou l’autre à la compilation. Si le type exact de vecteur ne peut être déterminé qu’à l’exécution, c’est cuit! On en revient à un besoin de double dispatch (qui écarte la mixité — bouhhh!). On peut le résoudre avec un downcasting (mais qu’est-ce que ce code ressemble à l’implémentation de equals du monde Java!…), ou avec des visiteurs.

Quitte à tout casser, migrer à eigen n’est vraiment pas possible?

Merci !
Je pense que que le type V est connu à la compilation. Cependant, DistributedVector (je crois que cette classe a été ajoutée par les premiers développeurs de notre code) est une structure arborescente dont les membres sont eux-mêmes des Vector, dont les types ne sont connus qu’à l’exécution… et là c’est le drame.

Je vais en discuter avec mon collègue ;) On va jeter un oeil à Eigen également.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte