Optimisation qui ne marche pas comme il faut

"ne pas refaire ce qui a déjà été fait"

a marqué ce sujet comme résolu.

Mais rien ne permet de garantir que tu n'auras jamais une utilisation polymorphe de ta classe.

Si, si je suis le seul à l'utiliser… mais dans l'absolu tu as raison effectivement.

Je ne comprends pas en quoi la dérivation privée change quelque chose pour le destructeur non virtuel alors

+0 -0

Même quand tu es le seul à bosser sur un projet, il arrivera toujours un moment où tu te tromperas dans l'utilisation de tes propres classes (et en général de tes propres outils). ET là, tu te féliciteras si tu as suivi quelques bonnes pratiques à respecter. Ou alors tu t'arracheras les cheveux en débogage pendant des heures si tu ne les a pas respectées.

+0 -0

C'est donc à ça que sert l'héritage en private ! Je n'avais jamais compris l'utilité du truc en fait ^^

Plus précisément, il représente la relation EST IMPLENTE EN FONCTION DE, alors que l'héritage public représente la relation EST-UN. Ainsi, avec un héritage privé, tu peux réutiliser la classe de base pour ton implémentation, mais ce sera interne à ta classe (pas de polymorphisme, etc…) D'où l'appellation "héritage privé".

+0 -0

Parce que l'héritage privé interdit une utilisation polymorphe.

Ah, ok… je l'ignorais.

JE pensais que la seule différence entre dérivation publique, protégée et privée étaient les modalités d'accès aux méthodes héritées.

En terme d'implémentation, ils sont strictement équivalents.

Donc si je vous suis bien, l'héritage privé équivaut à une composition ?

Décidément, le C++ n'en aura jamais fini de délivrer ses petits secrets.

Même quand tu es le seul à bosser sur un projet, il arrivera toujours un moment où tu te tromperas dans l'utilisation de tes propres classes.

Ah ben non. SE planter avec le code et les bibliothèques des autres certainement, ça oui; tout le temps. Mais son propre code, en principe pas; sauf à ressortir du vieux code qui a pris la poussière pendant quelques années, après l'avoir totalement oublié au point de croire qu'il a été écrit par quelqu'un d'autre.

+0 -0

En terme d'implémentation, ils sont strictement équivalents.

Donc si je vous suis bien, l'héritage privé équivaut à une composition ?

Décidément, le C++ n'en aura jamais fini de délivrer ses petits secrets.

QuentinC

C'est l'idée, la différence tient dans le fait que les fonctions du composants s'appellent sur this et pas sur un composant nommé, et que l'on peut très simplement faire l'export des fonctions membres que l'on veut garder dans l'interface de la classe.

Bonsoir,

Intéressante discussion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <vector>

class MyVectorInt : private std::vector<int>{
public:
  using std::vector<int>::vector;
  using std::vector<int>::operator[];
  using std::vector<int>::size;
  using std::vector<int>::begin;
  using std::vector<int>::end;
  //etc ...
};

Ksass`Peuk

J'ai essayé de suivre la recette et d'ajouter operator==, mais pouf :

1
2
main.cpp:10:35: error: no members matching 'std::vector<int>::operator==' in 'class std::vector<int>'
   using std::vector<int>::operator==;
+0 -0

C'est donc à ça que sert l'héritage en private ! Je n'avais jamais compris l'utilité du truc en fait ^^

Plus précisément, il représente la relation EST IMPLENTE EN FONCTION DE, alors que l'héritage public représente la relation EST-UN. Ainsi, avec un héritage privé, tu peux réutiliser la classe de base pour ton implémentation, mais ce sera interne à ta classe (pas de polymorphisme, etc…) D'où l'appellation "héritage privé".

mehdidou99

Et du coup, le l'héritage en protected se place comment là dedans ?

+0 -0

@yoch : les opérateurs de comparaison de vector ne sont pas des fonctions membres (quand on a le choix, il vaut mieux effectivement faire des fonctions libres). Pour celle-ci, on n'a effectivement pas d'autre choix que les implémenter. Mais pour le coup, on s'en sort bien parce que lexicographical_compare est une fonction de la bibliothèque standard, de même que equals ;) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
bool operator==(MyVectorInt const& v0, MyVectorInt const& v1){
  return std::equals(v0.begin(), v0.end(), v1.begin(), v1.end());
}
bool operator!=(MyVectorInt const& v0, MyVectorInt const& v1){
  return !(v0 == v1);
}
bool operator<=(MyVectorInt const& v0, MyVectorInt const& v1){
  return std::lexicographical_compare(v0.begin(), v0.end(), 
                                      v1.begin(), v1.end(), 
                                      std::less_equal<int>());
}
bool operator>(MyVectorInt const& v0, MyVectorInt const& v1){
  return !(v0 <= v1);
}
//pareil dans l'autre sens pour >= et <.

@Luthaf :

(ndlr : concernant l'héritage) protégé : si vous avez une bonne raison, la rédaction sera curieuse de la connaître.

FaQ développez.com

@mehdidou99 : J'ai juste mis ça pour l'exemple de comment on peut faire. Mettre un using pour le type de l'héritage faciliterait effectivement une refactorisation dans un tel cas.

Merci pour toutes ces explications techniques.

Tout ça m'a donné l'idée de tenter une dernière micro-optimisation : utiliser std::array au lieu de std::vector. Ca améliore encore sympathiquement les perfs. (Il y a aussi deux lookup tables pour le blocage des lignes/colonnes).

D'autres trucs à redire sur le code ? J'ai rendu l'héritage private pour ne pas me faire châtier trop lourdement cette fois-ci…

Ah, et puis j'ai maintenant un petit problème, je n'arrive plus à utiliser une initializer list pour mon Board, j'ai dû faire un hack pourri (mettre dans un vecteur puis std::copy).

Ah, et puis j'ai maintenant un petit problème, je n'arrive plus à utiliser une initializer list pour mon Board, j'ai dû faire un hack pourri (mettre dans un vecteur puis std::copy).

yoch

Tu n'as pas importé les constructeurs de array ;) .

1
using std::array<Block,M*M>::array;

D'autres trucs à redire sur le code ?

yoch

  • Compilation : 4 warnings faciles à corriger,
  • Général : peut être qu'il y a moyen de taguer des choses "constexpr",
  • (Mineur : syntaxe) Dans le constructeur de block, full peut être initialisé dans la liste d'initialisation,
  • (Cohérence) Une raison particulière pour prendre le type "int" pour row et col ? (un type non signé serait plus cohérent),
  • (Important : perf) Dans la classe board, tu as deux fontions qui reçoivent des vectors en entrée par copie mais ne font pas de modifications dessus : passe les par référence const,
  • (Mineur : homogénéité) columns et row pourrait aussi être des std:array,
  • (Mineur : cohérence) fonction simulate, rb-loop moves : "auto const&",
  • (Important : perf) conséquence du point précédent : moves n'est pas modifié : passage par const&,
  • (Mineur : bonne-pratique) "auto" pour les temporaires/constantes,
  • (Mineur : factorisation) la fonction check_solution est découpable/factorisable.

(Oui, je suis un tyran).

Merci pour le retour :) .

Ah, et puis j'ai maintenant un petit problème, je n'arrive plus à utiliser une initializer list pour mon Board, j'ai dû faire un hack pourri (mettre dans un vecteur puis std::copy).

Tu n'as pas importé les constructeurs de array ;) .

1
using std::array<Block,M*M>::array;

J'avais déja essayé avec aussi, mais ça n'aide pas (source ici).

1
2
3
4
main.cpp: In function 'int main()':
main.cpp:426:5: error: could not convert '{Block(4), Block(16), Block(4), Block(16), Block(4), Block(16), Block(4), Block(2), Block(4), Block(16), Block(4), Block(2), Block(4), Block(2), Block(4), Block(16), Block(4), Block(2), Block(4), Block(16), Block(4), Block(16), Block(4), Block(16), Block(4)}' from '<brace-enclosed initializer list>' to 'Board<5u>'
     };
     ^
  • (Important : perf) Dans la classe board, tu as deux fontions qui reçoivent des vectors en entrée par copie mais ne font pas de modifications dessus : passe les par référence const,

Pas fait gaffe, mais comme on n'utilise ces fonctions qu'à l'initialisation c'est pas dramatique en pratique.

  • (Mineur : homogénéité) columns et row pourrait aussi être des std:array,

J'avais peur qu'il se comporte comme vector<bool>, mais en fait non (heureusement).

  • (Mineur : factorisation) la fonction check_solution est découpable/factorisable.

Je voudrais être sûr que le code soit inliné, c'est une fonction critique pour les perfs.

OK pour le reste.

+0 -0

Ok. Compris le problème. J'avais oublié que array ne possédait pas de constructeur par initializer-list, sa construction par séquence entre accolades tient dans le fait que c'est un agrégat des tableaux C statiques.

Il y a diverses solutions, la plus élégante est galère à mettre en place, elle consiste à utiliser un constructeur template variadique pour rediriger les arguments en perfect-forwarding à la construction. Mais après c'est la croix et la bannière pour retrouver la copie et le déplacement.

L'autre solution est moins élégant mais plus simple : on implémente un constructeur prenant en entrée un std::array et on précise la valeur entre accolades à la construction comme étant un std::array.

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