Pour éviter la duplication non triviale liée à la const C ++, existe-t-il des cas où const_cast fonctionnerait mais une fonction const privée renvoyant non const ne le serait pas?
Dans l' article efficace C ++ de Scott Meyers 3, il suggère qu'un const_cast combiné avec un transtypage statique peut être un moyen efficace et sûr d'éviter le code en double, par exemple
const void* Bar::bar(int i) const
{
...
return variableResultingFromNonTrivialDotDotDotCode;
}
void* Bar::bar(int i)
{
return const_cast<void*>(static_cast<const Bar*>(this)->bar(i));
}
Meyers poursuit en expliquant que le fait d'appeler la fonction const la fonction non-const est dangereux.
Le code ci-dessous est un contre-exemple montrant:
- contrairement à la suggestion de Meyers, parfois le const_cast combiné avec un cast statique est dangereux
- avoir parfois la fonction const appeler le non-const est moins dangereux
- parfois dans les deux sens en utilisant un const_cast masquer les erreurs de compilation potentiellement utiles
- éviter un const_cast et avoir un membre privé const supplémentaire retournant un non-const est une autre option
L'une des stratégies const_cast pour éviter la duplication de code est-elle considérée comme une bonne pratique? Préférez-vous plutôt la stratégie de méthode privée? Y a-t-il des cas où const_cast fonctionnerait mais pas une méthode privée? Existe-t-il d'autres options (outre la duplication)?
Ma préoccupation avec les stratégies const_cast est que même si le code est correct lors de l'écriture, plus tard pendant la maintenance, le code pourrait devenir incorrect et le const_cast cacherait une erreur de compilation utile. Il semble qu'une fonction privée commune soit généralement plus sûre.
class Foo
{
public:
Foo(const LongLived& constLongLived, LongLived& mutableLongLived)
: mConstLongLived(constLongLived), mMutableLongLived(mutableLongLived)
{}
// case A: we shouldn't ever be allowed to return a non-const reference to something we only have a const reference to
// const_cast prevents a useful compiler error
const LongLived& GetA1() const { return mConstLongLived; }
LongLived& GetA1()
{
return const_cast<LongLived&>( static_cast<const Foo*>(this)->GetA1() );
}
/* gives useful compiler error
LongLived& GetA2()
{
return mConstLongLived; // error: invalid initialization of reference of type 'LongLived&' from expression of type 'const LongLived'
}
const LongLived& GetA2() const { return const_cast<Foo*>(this)->GetA2(); }
*/
// case B: imagine we are using the convention that const means thread-safe, and we would prefer to re-calculate than lock the cache, then GetB0 might be correct:
int GetB0(int i) { return mCache.Nth(i); }
int GetB0(int i) const { return Fibonachi().Nth(i); }
/* gives useful compiler error
int GetB1(int i) const { return mCache.Nth(i); } // error: passing 'const Fibonachi' as 'this' argument of 'int Fibonachi::Nth(int)' discards qualifiers
int GetB1(int i)
{
return static_cast<const Foo*>(this)->GetB1(i);
}*/
// const_cast prevents a useful compiler error
int GetB2(int i) { return mCache.Nth(i); }
int GetB2(int i) const { return const_cast<Foo*>(this)->GetB2(i); }
// case C: calling a private const member that returns non-const seems like generally the way to go
LongLived& GetC1() { return GetC1Private(); }
const LongLived& GetC1() const { return GetC1Private(); }
private:
LongLived& GetC1Private() const { /* pretend a bunch of lines of code instead of just returning a single variable*/ return mMutableLongLived; }
const LongLived& mConstLongLived;
LongLived& mMutableLongLived;
Fibonachi mCache;
};
class Fibonachi
{
public:
Fibonachi()
{
mCache.push_back(0);
mCache.push_back(1);
}
int Nth(int n)
{
for (int i=mCache.size(); i <= n; ++i)
{
mCache.push_back(mCache[i-1] + mCache[i-2]);
}
return mCache[n];
}
int Nth(int n) const
{
return n < mCache.size() ? mCache[n] : -1;
}
private:
std::vector<int> mCache;
};
class LongLived {};
Réponses:
Lors de l'implémentation de fonctions membres const et non const qui ne diffèrent que par le fait que le ptr / référence retourné est const, la meilleure stratégie DRY consiste à:
par exemple
Appelons cela la fonction const privée retournant un motif non const .
Il s'agit de la meilleure stratégie pour éviter les doublons de manière simple tout en permettant au compilateur d'effectuer des vérifications potentiellement utiles et de signaler des messages d'erreur liés à const.
la source
const
instance (à moins que la référence ne soit à quelque chose de déclarémutable
, ou à moins que vous n'utilisiez unconst_cast
mais dans les deux cas il n'y a pas de probkem pour commencer ). De plus, je n'ai rien trouvé sur la "fonction const privée renvoyant un motif non const" (si c'était une blague pour l'appeler motif ... ce n'est pas drôle;)Oui, vous avez raison: de nombreux programmes C ++ qui tentent de corriger la const sont en violation flagrante du principe DRY, et même le membre privé qui retourne non const est un peu trop complexe pour le confort.
Cependant, vous manquez une observation: la duplication de code en raison de la const-correct n'est toujours un problème que si vous donnez à d'autres membres l'accès au code. Cela en soi est en violation de l'encapsulation. Généralement, ce type de duplication de code se produit principalement dans des accesseurs simples (après tout, vous donnez accès à des membres déjà existants, la valeur de retour n'est généralement pas le résultat d'un calcul).
D'après mon expérience, les bonnes abstractions n'ont pas tendance à inclure les accesseurs. Par conséquent, j'évite largement ce problème en définissant des fonctions membres qui font réellement quelque chose, plutôt que de simplement fournir un accès aux membres de données; J'essaie de modéliser le comportement au lieu des données. Mon intention principale dans ce cas est d'obtenir en fait une certaine abstraction de mes classes et de leurs fonctions membres individuelles, au lieu d'utiliser simplement mes objets comme conteneurs de données. Mais ce style réussit également très bien à éviter les tonnes d'accesseurs répétitifs const / non const qui sont si courants dans la plupart des codes.
la source