web-dev-qa-db-fra.com

Solution élégante pour dupliquer, const et non const, getters?

Ne déteste-tu pas quand tu as

class Foobar {
public:
    Something& getSomething(int index) {
        // big, non-trivial chunk of code...
        return something;
    }

    const Something& getSomething(int index) const {
        // big, non-trivial chunk of code...
        return something;
    }
}

Nous ne pouvons implémenter aucune de ces méthodes avec l'autre, car vous ne pouvez pas appeler la version nonconst à partir de la version const (erreur du compilateur). Un cast sera nécessaire pour appeler la version const à partir de la version non -const.

Existe-t-il une vraie solution élégante à cela, sinon, quelle est la plus proche de celle-ci?

111
Michael

Je me souviens d'un des livres de C++ efficace que la façon de le faire est d'implémenter la version non-const en supprimant le const de l'autre fonction.

Ce n'est pas particulièrement joli, mais c'est sûr. Étant donné que la fonction membre qui l'appelle est non-const, l'objet lui-même est non-const et le rejet de la const est autorisé.

class Foo
{
public:
    const int& get() const
    {
        //non-trivial work
        return foo;
    }

    int& get()
    {
        return const_cast<int&>(const_cast<const Foo*>(this)->get());
    }
};
99
CAdaker

Que diriez-vous:

template<typename IN, typename OUT>
OUT BigChunk(IN self, int index) {
    // big, non-trivial chunk of code...
    return something;
}

struct FooBar {
    Something &getSomething(int index) {
        return BigChunk<FooBar*, Something&>(this,index);
    }

    const Something &getSomething(int index) const {
        return BigChunk<const FooBar*, const Something&>(this,index);
    }
};

De toute évidence, vous aurez toujours la duplication de code objet, mais pas de duplication de code source. Contrairement à l'approche const_cast, le compilateur vérifiera votre const-correctness pour les deux versions de la méthode.

Vous devez probablement déclarer les deux instanciations intéressantes de BigChunk comme amis de la classe. C'est une bonne utilisation de friend, car les fonctions friend sont cachées près du friendee, il n'y a donc aucun risque de couplage sans contrainte (ooh-er!). Mais je n'essaierai pas la syntaxe pour le faire maintenant. N'hésitez pas à ajouter.

Il y a des chances que BigChunk ait besoin de se déférer, auquel cas l'ordre de définition ci-dessus ne fonctionnera pas très bien, et certaines déclarations avancées seront nécessaires pour le trier.

De plus, afin d'éviter une recherche vide de BigChunk dans l'en-tête et de décider d'instancier et de l'appeler même si c'est moralement privé, vous pouvez déplacer le tout dans le fichier cpp pour FooBar. Dans un espace de noms anonyme. Avec liaison interne. Et un panneau disant "méfiez-vous du léopard".

27
Steve Jessop

Je jetterais le const au non-const (deuxième option).

7
Matthew Flaschen

Pourquoi ne pas simplement extraire le code commun dans une fonction privée distincte, puis demander aux deux autres de l'appeler?

5
Jay

Essayez d'éliminer les getters en refactorisant votre code. Utilisez des fonctions ou des classes d'amis si seulement un très petit nombre d'autres choses ont besoin de quelque chose.

En général, les Getters et Setters rompent l'encapsulation car les données sont exposées au monde. L'utilisation d'un ami n'expose les données qu'à quelques privilégiés, ce qui permet une meilleure encapsulation.

Bien sûr, ce n'est pas toujours possible, vous pouvez donc être coincé avec les getters. À tout le moins, la plupart ou la totalité du "morceau de code non trivial" devrait être dans une ou plusieurs fonctions privées, appelées par les deux getters.

4
markh44

La référence const à l'objet est logique (vous mettez une restriction sur l'accès en lecture seule à cet objet), mais si vous devez autoriser une référence nonconst, vous pourriez aussi bien bien rendre le membre public.

Je crois que c'est à la Scott Meyers (Efficient C++).

3
swongu

Le concept de "const" est là pour une raison. Pour moi, il établit un contrat très important sur la base duquel des instructions supplémentaires d'un programme sont écrites. Mais vous pouvez faire quelque chose sur les lignes suivantes: -

  1. rendre votre membre "mutable"
  2. faire le const 'getters'
  3. renvoyer une référence non constante

Avec cela, on peut utiliser une référence const sur le LHS si vous avez besoin de maintenir la fonctionnalité const où vous utilisez le getter avec l'utilisation non const (dangereux). Mais il incombe maintenant au programmeur de maintenir les invariants de classe.

Comme cela a été dit dans SO avant, rejetant la constance d'un objet const initialement défini et l'utilisant est un UB donc je n'utiliserais pas de transtypages. Faire également un objet non const const puis à nouveau rejeter la constance ne serait pas trop beau.

Une autre directive de codage que j'ai vue utilisée dans certaines équipes est: -

  • Si une variable membre doit être modifiée en dehors de la classe, renvoyez toujours un pointeur vers elle via une fonction membre non const.
  • Aucune fonction membre ne peut renvoyer de références non const. Seules les références const sont autorisées dans les fonctions membres const.

Cela permet une certaine cohérence dans la base de code globale et l'appelant peut clairement voir quels appels peuvent modifier la variable membre.

2
Abhay