272 votes

Comment puis-je supprimer duplication de code entre const semblable et non const fonctions membres ?

Disons que j’ai la classe suivante X où je veux regagner accès membre interne :

Les deux membres fonctions « X::Z() », « X::Z() const » ont un code identique à l’intérieur des accolades. Il s’agit de duplication de code et peuvent causer des problèmes de maintenance pour les fonctions de longues avec une logique complexe.

Y a-t-il un moyen d’éviter cette duplication de code ?

210voto

jwfearn Points 8813

Pour une explication plus détaillée, veuillez consulter la rubrique "Éviter la Duplication const et Non-const Fonction Membre" à la p. 23, à l'Article 3 "Utilisation const chaque fois que possible," Effective C++, 3d ed par Scott Meyers, ISBN-13: 9780321334879.

alt text

Voici Meyers solution (simplifié):

struct C {
    const char & get() const { return c; }
    char & get() {
        return const_cast<char &>( static_cast<const C &>( *this ).get() );
    }
    char c;
};

Les deux moulages et appel de fonction peut être moche, mais c'est correct. Meyers a une très bonne explication pourquoi.

70voto

Kevin Points 7334

Oui, il est possible d'éviter la duplication de code. Vous devez utiliser la fonction membre const avoir de la logique et de la non-const appel de fonction membre const fonction de membre et re-fonte de la valeur de retour d'une non-const de référence (pointeur ou si la fonction retourne un pointeur):

class X
{
   std::vector<Z> vecZ;

public:
   const Z& Z(size_t index) const
   {
      // same really-really-really long access 
      // and checking code as in OP
      // ...
      return vecZ[index];
   }

   Z& Z(size_t index)
   {
      // One line. One ugly, ugly line - but just one line!
      return const_cast<Z&>( static_cast<const X&>(*this).Z(index) );
   }

 #if 0 // A slightly less-ugly version
   Z& Z(size_t index)
   {
      // Two lines -- one cast. This is slightly less ugly but takes an extra line.
      const X& constMe = *this;
      return const_cast<Z&>( constMe.Z(index) );
   }
 #endif
};

REMARQUE: Il est important pour vous de ne PAS mettre de la logique dans le non-const fonction et la const-appel de la fonction le non-const fonction -- cela peut entraîner un comportement indéfini. La raison en est qu'une constante instance de classe est rejeté comme non-constante de l'instance. Le non-const fonction membre peut modifier accidentellement la classe, la norme C++ états entraînera un comportement indéfini.

37voto

Pait Points 174

Je pense que Scott Meyers solution peut être améliorée en C++11 en utilisant un avec fonction d'assistance. Cela rend l'intention d'autant plus évident et peut être réutilisé pour de nombreuses autres méthodes.

template <typename T>
struct NonConst {typedef T type;};
template <typename T>
struct NonConst<T const> {typedef T type;}; //by value
template <typename T>
struct NonConst<T const&> {typedef T& type;}; //by reference
template <typename T>
struct NonConst<T const*> {typedef T* type;}; //by pointer
template <typename T>
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference

template<typename TConstReturn, class TObj, typename... TArgs>
typename NonConst<TConstReturn>::type likeConstVersion(
   TObj const* obj,
   TConstReturn (TObj::* memFun)(TArgs...) const,
   TArgs... args) {
      return const_cast<typename NonConst<TConstReturn>::type>(
         (obj->*memFun)(args...));
}

Cette fonction d'aide peut être utilisée de la manière suivante.

struct T {
   int arr[100];

   int const& getElement(size_t i) const{
      return arr[i];
   }

   int& getElement(size_t i) {
      return likeConstVersion(this, &T::getElement, i);
   }
};

Le premier argument est toujours le cette-pointeur. Le second est un pointeur vers la fonction de membre de l'appeler. Après qu'une quantité arbitraire d'arguments supplémentaires peuvent être transmises afin qu'ils puissent être transmis à la fonction. Ce besoin de C++11 en raison de la variadic templates.

22voto

Steve Jessop Points 166970

Un peu plus verbeux que Meyers, mais je pourrais faire ceci:

class X {

    private:

    // This method MUST NOT be called except from boilerplate accessors.
    Z &_getZ(size_t index) const {
        return something;
    }

    // boilerplate accessors
    public:
    Z &getZ(size_t index)             { return _getZ(index); }
    const Z &getZ(size_t index) const { return _getZ(index); }
};

La méthode privée a la propriété indésirable qu'il renvoie d'un non-const Z& pour un const exemple, qui est pourquoi il est privé. Les méthodes privées peuvent casser les invariants de l'interface externe (dans ce cas, le désiré invariant est "un const objet ne peut pas être modifié via des références obtenues grâce à des objets qu'il a-un").

Notez que les commentaires font partie de la structure même du _getZ de l'interface spécifie qu'il n'est jamais valide pour l'appeler (à part les accesseurs, évidemment): il n'y a pas concevable d'avantages à le faire de toute façon, parce que c'est 1 plus de caractère de type et ne se traduira pas dans les plus petites ou plus de code. L'appel de la méthode est équivalent à l'appel de l'un des accesseurs avec un const_cast, et vous ne voulez pas le faire. Si vous êtes inquiet au sujet de faire des erreurs évidentes (et c'est un gros objectif), puis l'appeler const_cast_getZ au lieu de _getZ.

En passant, j'apprécie Meyers de la solution. Je n'ai pas d'objection philosophique. Personnellement, cependant, je préfère un tout petit peu de contrôlée de la répétition, et une méthode privée qui ne doit être appelé dans certains étroitement des conditions contrôlées, sur une méthode qui ressemble à un bruit de ligne. Choisissez votre poison et le bâton avec elle.

[Edit: Kevin a relevé à juste titre que _getZ voudrez peut-être appeler une autre méthode (dire generateZ) qui est const spécialisé de la même façon getZ est. Dans ce cas, _getZ serait de voir un const Z& et ont pour const_cast avant de revenir. C'est encore sûr, car le standard de l'accesseur politiques de tout, mais c'est pas extrêmement évident que c'est sécuritaire. En outre, si vous le faites, puis plus tard, changement generateZ toujours y revenir const, alors vous aussi besoin de changer getZ toujours y revenir const, mais le compilateur ne vais pas vous dire que vous faites.

Ce dernier point sur le compilateur est également vrai de Meyers est recommandé de modèle, mais le premier point à propos d'un non-évident const_cast ne l'est pas. Donc sur la balance, je pense que si _getZ s'avère besoin d'un const_cast pour sa valeur de retour, alors ce modèle perd beaucoup de sa valeur sur Meyers. Depuis, il souffre aussi des inconvénients par rapport aux Meyers, je pense que je voudrais passer à son dans cette situation. Refactoring de l'un à l'autre est facile-il n'affecte pas les autres code valide dans la classe, puisque seul le code non valide et le passe-partout des appels _getZ.]

8voto

Andy Balaam Points 1491

Vous pouvez également résoudre ce avec des modèles. Cette solution est un peu moche (mais la laideur est caché dans le .fichier cpp) mais elle n'compilateur de vérifier constness, et pas de duplication de code.

.h fichier:

#include <vector>

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    const std::vector<Z>& GetVector() const { return vecZ; }
    std::vector<Z>& GetVector() { return vecZ; }

    Z& GetZ( size_t index );
    const Z& GetZ( size_t index ) const;
};

.fichier cpp:

#include "constnonconst.h"

template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
    // ... massive amounts of code ...

    // Note you may only use methods of X here that are
    // available in both const and non-const varieties.

    Child& ret = parent->GetVector()[index];

    // ... even more code ...

    return ret;
}

Z& X::GetZ( size_t index )
{
    return GetZImpl< X*, Z >( this, index );
}

const Z& X::GetZ( size_t index ) const
{
    return GetZImpl< const X*, const Z >( this, index );
}

Le principal inconvénient que je peux voir, c'est que parce que tous les complexes de mise en œuvre de la méthode est une fonction globale, vous avez besoin soit de se procurer les membres de X à l'aide de méthodes publiques comme GetVector() ci-dessus (dont il y a toujours besoin d'être un const et non const version), ou vous pourriez faire de cette fonction un ami. Mais je n'aime pas les amis.

[Edit: suppression de l'inutile de l'inclure des cstdio ajouté au cours de l'essai.]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X