Supposons que je dispose des éléments suivants class X
où je souhaite retourner l'accès à un membre interne:
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
Z& Z(size_t index)
{
// massive amounts of code for validating index
Z& ret = vecZ[index];
// even more code for determining that the Z instance
// at index is *exactly* the right sort of Z (a process
// which involves calculating leap years in which
// religious holidays fall on Tuesdays for
// the next thousand years or so)
return ret;
}
const Z& Z(size_t index) const
{
// identical to non-const X::Z(), except printed in
// a lighter shade of gray since
// we're running low on toner by this point
}
};
Les deux fonctions membres X::Z()
et X::Z() const
ont un code identique à l'intérieur des accolades. Il s'agit de code en double et peut entraîner des problèmes de maintenance pour les fonctions longues avec une logique complexe .
Existe-t-il un moyen d'éviter cette duplication de code?
Réponses:
Pour une explication détaillée, veuillez consulter la rubrique «Éviter la duplication dans les fonctions
const
nonconst
membres», à la p. 23, dans le point 3 «Utiliserconst
autant que possible», dans Effective C ++ , 3ème éd par Scott Meyers, ISBN-13: 9780321334879.Voici la solution de Meyers (simplifiée):
Les deux transtypages et l'appel de fonction peuvent être laids mais c'est correct. Meyers a une explication approfondie pourquoi.
la source
get()const
renvoie quelque chose qui a été défini comme un objet const, alors il ne devrait pas y avoir de version non-const duget()
tout. En fait, ma pensée à ce sujet a changé au fil du temps: la solution de modèle est le seul moyen d'éviter la duplication et d' obtenir une const-correct vérifiée par le compilateur, donc personnellement, je n'utiliserais plus unconst_cast
pour éviter la duplication de code, je choisirais entre mettre le code dupé dans un modèle de fonction ou bien le laisser dupé.template<typename T> const T& constant(T& _) { return const_cast<const T&>(_); }
ettemplate<typename T> T& variable(const T& _) { return const_cast<T&>(_); }
. Ensuite, vous pouvez faire:return variable(constant(*this).get());
Oui, il est possible d'éviter la duplication de code. Vous devez utiliser la fonction membre const pour avoir la logique et demander à la fonction membre non const d'appeler la fonction membre const et de recréer la valeur de retour en une référence non const (ou un pointeur si les fonctions renvoient un pointeur):
Remarque: il est important que vous ne mettez pas la logique dans la fonction non-const et que la fonction const appelle la fonction non-const - cela peut entraîner un comportement non défini. La raison en est qu'une instance de classe constante est convertie en instance non constante. La fonction membre non const peut modifier accidentellement la classe, ce que les états standard C ++ entraîneront un comportement non défini.
la source
C ++ 17 a mis à jour la meilleure réponse à cette question:
Cela présente les avantages suivants:
volatile
par accident, maisvolatile
est un qualificatif rare)Si vous souhaitez suivre la voie de déduction complète, cela peut être accompli en ayant une fonction d'aide
Maintenant, vous ne pouvez même plus gâcher
volatile
et l'utilisation ressemble àla source
f()
renvoie à laT
place deT&
.f()
retoursT
, nous ne voulons pas avoir deux surcharges, laconst
version seule est suffisante.shared_ptr
. Donc, ce dont j'avais réellement besoin était quelque choseas_mutable_ptr
qui ressemble presque à ce quias_mutable
précède, sauf qu'il prend et renvoie unshared_ptr
et utilisestd::const_pointer_cast
au lieu deconst_cast
.T const*
cela se lierait àT const* const&&
plutôt que de se lier àT const* const&
(du moins dans mes tests, il l'a fait). J'ai dû ajouter une surcharge pourT const*
comme type d'argument pour les méthodes renvoyant un pointeur.Je pense que la solution de Scott Meyers peut être améliorée en C ++ 11 en utilisant une fonction d'assistance tempate. Cela rend l'intention beaucoup plus évidente et peut être réutilisé pour de nombreux autres getters.
Cette fonction d'assistance peut être utilisée de la manière suivante.
Le premier argument est toujours le pointeur this. Le second est le pointeur sur la fonction membre à appeler. Après cela, une quantité arbitraire d'arguments supplémentaires peut être transmise afin qu'ils puissent être transmis à la fonction. Cela nécessite C ++ 11 en raison des modèles variadic.
la source
std::remove_bottom_const
à y allerstd::remove_const
.const_cast
. Vous pouvez créergetElement
un modèle lui-même et utiliser le trait du type à l'intérieur pour lesmpl::conditional
types dont vous avez besoin, commeiterator
s ouconstiterator
s si nécessaire. Le vrai problème est de savoir comment générer une version const d'une méthode lorsque cette partie de la signature ne peut pas être modélisée?std::remove_const<int const&>
estint const &
(supprimer laconst
qualification de haut niveau ), d'où la gymnastiqueNonConst<T>
dans cette réponse. Le putatifstd::remove_bottom_const
pourrait supprimer laconst
qualification de bas niveau et faire exactement ce qui seNonConst<T>
passe ici:std::remove_bottom_const<int const&>::type
=>int&
.getElement
est surchargée. Ensuite, le pointeur de fonction ne peut pas être résolu sans donner explicitement les paramètres du modèle. Pourquoi?likeConstVersion(TObj const* obj, TConstReturn (TObj::*memFun)(TArgs...) const, TArgs&&... args) { return const_cast<typename NonConst<TConstReturn>::type>((obj->*memFun)(std::forward<TArgs>(args)...)); }
Complete: gist.github.com/BlueSolei/bca26a8590265492e2f2760d3cefcf83Un peu plus verbeux que Meyers, mais je pourrais faire ceci:
La méthode private a la propriété indésirable de renvoyer un Z & non const pour une instance const, c'est pourquoi elle est privée. Les méthodes privées peuvent casser les invariants de l'interface externe (dans ce cas, l'invariant souhaité est "un objet const ne peut pas être modifié via des références obtenues via lui vers les objets qu'il a-a").
Notez que les commentaires font partie du modèle - l'interface de _getZ spécifie qu'il n'est jamais valide de l'appeler (à part les accesseurs, évidemment): il n'y a aucun avantage concevable à le faire de toute façon, car c'est un caractère de plus à taper et ne le fera pas entraîner un code plus petit ou plus rapide. Appeler la méthode équivaut à appeler l'un des accesseurs avec un const_cast, et vous ne voudriez pas le faire non plus. Si vous craignez de rendre les erreurs évidentes (et c'est un objectif équitable), appelez-le const_cast_getZ au lieu de _getZ.
Soit dit en passant, j'apprécie la solution de Meyers. Je n'ai aucune objection philosophique à lui. Personnellement, cependant, je préfère un tout petit peu de répétition contrôlée et une méthode privée qui ne doit être appelée que dans certaines circonstances étroitement contrôlées, plutôt qu'une méthode qui ressemble à du bruit de ligne. Choisissez votre poison et respectez-le.
[Edit: Kevin a souligné à juste titre que _getZ pourrait vouloir appeler une autre méthode (disons generateZ) qui est const-spécialisée de la même manière que getZ. Dans ce cas, _getZ verrait un const Z & et devrait le const_cast avant de revenir. C'est toujours sûr, car l'accessoire passe-partout règle tout, mais il n'est pas extrêmement évident qu'il soit sûr. De plus, si vous faites cela et que vous modifiez ensuite generateZ pour toujours retourner const, vous devez également changer getZ pour toujours retourner const, mais le compilateur ne vous le dira pas.
Ce dernier point sur le compilateur est également vrai pour le modèle recommandé par Meyers, mais le premier point sur un const_cast non évident ne l'est pas. Donc, tout compte fait, je pense que si _getZ s'avère avoir besoin d'un const_cast pour sa valeur de retour, alors ce modèle perd beaucoup de sa valeur par rapport à Meyers. Puisqu'il souffre également d'inconvénients par rapport à Meyers, je pense que je passerais au sien dans cette situation. La refactorisation de l'un à l'autre est facile - elle n'affecte aucun autre code valide de la classe, car seul le code non valide et le passe-partout appellent _getZ.]
la source
something
dans la_getZ()
fonction se trouve une variable d'instance? Le compilateur (ou au moins certains compilateurs) se plaindra du fait que puisque_getZ()
c'est const, toute variable d'instance référencée à l'intérieur l'est aussi const. Il ensomething
serait de même pour const (il serait de typeconst Z&
) et ne pourrait pas être converti enZ&
. D'après mon expérience (certes quelque peu limitée), la plupart du tempssomething
est une variable d'instance dans des cas comme celui-ci.const_cast
. Il était destiné à être un espace réservé pour le code requis pour obtenir un retour non const de l'objet const, et non pas un espace réservé pour ce qui aurait été dans le getter dupliqué. Donc, "quelque chose" n'est pas seulement une variable d'instance.Belle question et belles réponses. J'ai une autre solution, qui n'utilise aucun casting:
Cependant, il a la laideur d'exiger un membre statique et la nécessité d'utiliser la
instance
variable à l'intérieur.Je n'ai pas considéré toutes les implications (négatives) possibles de cette solution. Faites-le moi savoir s'il y en a.
la source
auto get(std::size_t i) -> auto(const), auto(&&)
. Pourquoi '&&'? Ahh, donc je peux dire:auto foo() -> auto(const), auto(&&) = delete;
this
mot-clé. Je suggère quetemplate< typename T > auto myfunction(T this, t args) -> decltype(ident)
le mot-clé this soit reconnu comme l'argument d'instance d'objet implicite et que le compilateur reconnaisse que mafonction est un membre ouT
.T
sera déduit automatiquement sur le site de l'appel, qui sera toujours le type de la classe, mais avec une qualification cv gratuite.const_cast
celle) de permettre de reveniriterator
etconst_iterator
.static
peut être fait à la portée du fichier au lieu de la portée de la classe. :-)Vous pouvez également résoudre ce problème avec des modèles. Cette solution est légèrement laide (mais la laideur est cachée dans le fichier .cpp) mais elle permet au compilateur de vérifier la constance, et pas de duplication de code.
Fichier .h:
Fichier .cpp:
Le principal inconvénient que je peux voir est que parce que toute l'implémentation complexe de la méthode est dans une fonction globale, vous devez soit saisir les membres de X en utilisant des méthodes publiques comme GetVector () ci-dessus (dont il doit toujours y avoir un version const et non const) ou vous pourriez faire de cette fonction un ami. Mais je n'aime pas les amis.
[Modifier: suppression de l'inclusion inutile de cstdio ajoutée pendant le test.]
la source
const_cast
(qui pourrait accidentellement être utilisé pour quelque chose qui est canst effectivement censé être const à quelque chose qui est pas).Que diriez-vous de déplacer la logique dans une méthode privée, et de ne faire que les choses "obtenir la référence et retourner" à l'intérieur des getters? En fait, je serais assez confus au sujet des transtypages statiques et const à l'intérieur d'une simple fonction getter, et je considérerais cela laid, sauf dans des circonstances extrêmement rares!
la source
Est-ce de la triche d'utiliser le préprocesseur?
Ce n'est pas aussi sophistiqué que les modèles ou les transtypages, mais cela rend votre intention ("ces deux fonctions doivent être identiques") assez explicite.
la source
C'est surprenant pour moi qu'il y ait tant de réponses différentes, mais presque toutes reposent sur une magie de modèle lourde. Les modèles sont puissants, mais parfois les macros les battent avec concision. La polyvalence maximale est souvent obtenue en combinant les deux.
J'ai écrit une macro
FROM_CONST_OVERLOAD()
qui peut être placée dans la fonction non-const pour invoquer la fonction const.Exemple d'utilisation:
Implémentation simple et réutilisable:
Explication:
Comme indiqué dans de nombreuses réponses, le modèle typique pour éviter la duplication de code dans une fonction membre non const est le suivant:
Une grande partie de ce passe-partout peut être évitée en utilisant l'inférence de type. Tout d'abord,
const_cast
peut être encapsulé dansWithoutConst()
, ce qui infère le type de son argument et supprime le qualificatif const. Deuxièmement, une approche similaire peut être utiliséeWithConst()
pour qualifier const lethis
pointeur, ce qui permet d'appeler la méthode const-overloaded.Le reste est une simple macro qui préfixe l'appel avec le bon qualifié
this->
et supprime const du résultat. Étant donné que l'expression utilisée dans la macro est presque toujours un simple appel de fonction avec des arguments transmis 1: 1, les inconvénients des macros telles que l'évaluation multiple ne se déclenchent pas. Les points de suspension et__VA_ARGS__
peuvent également être utilisés, mais ne devraient pas être nécessaires car des virgules (comme les séparateurs d'arguments) apparaissent entre parenthèses.Cette approche présente plusieurs avantages:
FROM_CONST_OVERLOAD( )
const_iterator
,std::shared_ptr<const T>
, etc.). Pour cela, il suffit de surchargerWithoutConst()
les types correspondants.Limitations: cette solution est optimisée pour les scénarios où la surcharge non const fait exactement la même chose que la surcharge const, de sorte que les arguments peuvent être transmis 1: 1. Si votre logique diffère et que vous n'appelez pas la version const via
this->Method(args)
, vous pouvez envisager d'autres approches.la source
Pour ceux (comme moi) qui
voici une autre prise:
Il s'agit essentiellement d'un mélange des réponses de @Pait, @DavidStone et @ sh1 ( EDIT : et une amélioration de @cdhowie). Ce qu'il ajoute au tableau, c'est que vous vous en sortez avec une seule ligne de code supplémentaire qui nomme simplement la fonction (mais pas d'argument ou de duplication de type de retour):
Remarque: gcc ne parvient pas à compiler cela avant 8.1, clang-5 et plus ainsi que MSVC-19 sont satisfaits (selon l'explorateur du compilateur ).
la source
decltype()
s ne devraient-ils pas également être utilisésstd::forward
sur les arguments pour s'assurer que nous utilisons le bon type de retour dans le cas où nous avons des surchargesget()
qui prennent différents types de références?NON_CONST
macro déduit le type de retour de manière incorrecte etconst_cast
s au mauvais type en raison du manque de transfert dans lesdecltype(func(a...))
types. Les remplacer pardecltype(func(std::forward<T>(a)...))
résout ce problème . (Il y a juste une erreur de l'éditeur de liens car je n'ai jamais défini deX::get
surcharge déclarée .)Voici une version C ++ 17 de la fonction d'assistance statique du modèle, avec et un test SFINAE facultatif.
Version complète: https://godbolt.org/z/mMK4r3
la source
J'ai trouvé une macro qui génère automatiquement des paires de fonctions const / non const.
Voir la fin de la réponse pour l'implémentation.
L'argument de
MAYBE_CONST
est dupliqué. Dans la première copie,CV
est remplacé par rien; et dans le deuxième exemplaire, il est remplacé parconst
.Il n'y a pas de limite au nombre de fois qui
CV
peuvent apparaître dans l'argument macro.Il y a cependant un léger inconvénient. Si
CV
apparaît entre parenthèses, cette paire de parenthèses doit être préfixée avecCV_IN
:La mise en oeuvre:
Implémentation pré-C ++ 20 qui ne prend pas en charge
CV_IN
:la source
En règle générale, les fonctions membres pour lesquelles vous avez besoin de versions const et non const sont des getters et setters. La plupart du temps, il s'agit de lignes uniques, la duplication de code n'est donc pas un problème.
la source
J'ai fait cela pour un ami qui a justifié à juste titre l'utilisation de
const_cast
... sans le savoir, j'aurais probablement fait quelque chose comme ça (pas vraiment élégant):la source
Je suggère un modèle de fonction statique d'assistance privée, comme ceci:
la source
Cet article DDJ montre une manière d'utiliser la spécialisation de modèle qui ne vous oblige pas à utiliser const_cast. Pour une fonction aussi simple, elle n'est cependant pas vraiment nécessaire.
boost :: any_cast (à un moment donné, il n'en a plus) utilise un const_cast de la version const appelant la version non-const pour éviter la duplication. Vous ne pouvez pas imposer de sémantique const à la version non-const, donc vous devez être très prudent avec cela.
En fin de compte, une certaine duplication de code est correcte tant que les deux extraits sont directement l'un sur l'autre.
la source
Pour ajouter à la solution jwfearn et kevin fournie, voici la solution correspondante lorsque la fonction retourne shared_ptr:
la source
Je n'ai pas trouvé ce que je cherchais, alors j'en ai roulé quelques-uns ...
Celui-ci est un peu verbeux, mais a l'avantage de gérer de nombreuses méthodes surchargées du même nom (et type de retour) à la fois:
Si vous n'avez qu'une seule
const
méthode par nom, mais encore beaucoup de méthodes à dupliquer, vous préférerez peut-être ceci:Malheureusement, cela tombe en panne dès que vous commencez à surcharger le nom (la liste des arguments de l'argument du pointeur de fonction semble être non résolue à ce stade, donc il ne peut pas trouver de correspondance pour l'argument de fonction). Bien que vous puissiez également définir un moyen de vous en sortir:
Mais les arguments de référence à la
const
méthode ne correspondent pas aux arguments apparemment par valeur du modèle et il se casse.Pas certain de pourquoi.Voici pourquoi .la source