Question

Je travaille sur un code C ++ où j'ai plusieurs objets manager avec des méthodes privées telles que

void NotifyFooUpdated();

qui appelle la méthode de OnFooUpdated() sur les écouteurs de cet objet.

Notez qu'ils ne modifient pas l'état de cet objet, afin qu'ils puissent être techniquement fait des méthodes de const, même si elles modifient généralement l'état du système dans son ensemble. En particulier, les objets d'auditeur peut appeler de nouveau dans cet objet et le modifier.

Personnellement, je voudrais les laisser comme ils sont et non les déclarer const.

Cependant, notre code statique vérificateur drapeaux QAC cela comme un écart, je dois donc soit de les déclarer const, ou je dois faire valoir pourquoi ils devraient rester non-const et obtenir une subvention pour l'écart.

Quels sont les arguments pour ne pas déclarer ces méthodes const?
Ou devrais-je suivre et de les déclarer QAC const?
Dois-je adopter un point de vue strictement local restreint à cet objet, ou d'envisager dans son ensemble le système?

Était-ce utile?

La solution

Grosso modo, vous avez une classe de conteneur: Un gestionnaire complet d'observateurs. En C et C ++, vous pouvez avoir des conteneurs const avec des valeurs non-const. Considérez si vous avez supprimé une couche d'emballage:

list<Observer> someManager;

void NotifyFooUpdated(const list<Observer>& manager) { ... }

Vous verriez rien d'étrange à une NotifyFooUpdated globale prenant une liste de const, car elle ne modifie pas la liste. Cet argument const fait effectivement l'argument plus permissive analyse: La fonction accepte les const et les listes non-const. Toutes les annotations const sur les moyens de version de méthode de classe est const *this.

Pour aborder un autre point de vue:

  

Si vous ne pouvez pas garantir que l'objet que vous invoquez la fonction sur reste le même avant et après l'appel de fonction, vous devez laisser généralement que non const.

C'est raisonnable que si l'appelant a la seule référence à l'objet. Si l'objet est global (comme il est dans la question initiale) ou dans un environnement fileté, l'constness d'un appel donné ne garantit pas l'état de l'objet est inchangé dans l'appel. Une fonction sans effets secondaires et qui renvoie toujours la même valeur pour les mêmes entrées est pur . NotifyFooUpdate () est évidemment pas pure.

Autres conseils

Si les auditeurs sont stockés sous forme d'une collection de pointeurs que vous pouvez appeler une méthode non-const sur eux, même si votre objet est const.

Si le contrat est qu'un auditeur peut mettre à jour son état quand il reçoit une notification, la méthode doit être non-const.

Vous dites que l'auditeur peut appeler de nouveau dans l'objet et le modifier. Mais l'auditeur ne changera pas lui-même -. Si l'appel Notifier pourrait être const mais vous passez un pointeur non-const à votre propre objet dans ce

Si l'auditeur a déjà pointeur (il écoute seulement à une chose) alors vous pouvez faire les deux méthodes const, que votre objet se modifié est un effet secondaire. Ce qui se passe est:

A appelle B B modifie un à la suite.

Une demande conduit B indirectement à sa propre modification mais n'est pas une modification directe de soi.

Si tel est le cas aussi bien vos méthodes pourrait et devrait probablement const.

  

Quels sont les arguments pour ne pas déclarer ces méthodes const?
  Ou devrais-je suivre et de les déclarer QAC const?
  Dois-je adopter un point de vue strictement local restreint à cet objet, ou d'envisager dans son ensemble le système?

Qu'est-ce que vous savez est que cet objet gestionnaire cela a été appelé à do pas changement. Les objets du gestionnaire invoque alors les fonctions de peut changement ou ils ne pourraient pas. Vous ne savez pas.

D'après votre description, je pouvais imaginer la conception d'un tel où tous les objets impliqués sont const (et les notifications peuvent être traitées en les écrivant à la console). Si vous ne faites pas cette fonction const, vous interdisez cela. Si vous le faites const, vous permettez à la fois.

Je suppose que cela est un argument en faveur de ce qui en fait const.

Si vous ne pouvez pas garantir que l'objet a invoqué la fonction sur reste le même avant et après l'appel de fonction, vous devez laisser généralement que non const. Pensez-y - vous pourriez écrire un écouteur, insérez tandis que l'objet est non-const, puis utiliser cette fonction pour violer la justesse const parce que vous avez déjà eu accès à cet objet quand il était non-const dans le passé. Ce qui ne va pas.

Mon avis sur la question est qu'ils doivent rester non const . Ceci est basé sur ma perception que l'état de l'objet gestionnaire, est en fait l'ensemble des états de tous les objets qu'il gère plus tout état intrinsèque à savoir State(Manager) = State(Listener0) + State(Listener1) + ... + State(ListenerN) + IntrinsicState(Manager).

Alors que l'encapsulation dans le code source peut démentir cette relation d'exécution. D'après votre description, je crois que cet état agrégé est le reflet du comportement de l'exécution du programme.

Pour renforcer mon argumentation. J'affirme que le code devrait chercher à refléter les programmes d'exécution des comportements de préférence à l'adhésion stricte à la sémantique exacte de compilation

Pour const , ou non const :. Qui est la question

Arguments pour const:

  • Les méthodes en question ne modifie pas l'état de l'objet.
  • Vos drapeaux statiques vérificateur de code de l'absence de const comme un écart, peut-être vous devriez l'écouter.

Arguments contre const:

  • Les méthodes modifier l'état du système dans son ensemble.
  • objets Listener mon modifier l'objet.

Personnellement, je partirais avec le laisser comme const, le fait qu'il pourrait modifier l'état du système dans son ensemble est à peu près comme une référence de pointeur NULL. Il est une méthode de const, il ne modifie pas l'objet en question, mais il va se planter votre programme ce qui modifie l'état du système.

Il y a quelques bons arguments pour contre const , voici donc voici mon: -

Personnellement, je n'aurais pas ces « OnXXXUpdated » dans le cadre de mes cours de gestion. Je pense que c'est la raison pour laquelle il y a une certaine confusion quant à la meilleure pratique. Vous informer les parties intéressées au sujet de quelque chose, et ne savent pas si oui ou non l'état de l'objet va changer au cours du processus de notification. Il peut, ou non. Qu'est-ce que évident pour moi, est que le processus de notification aux parties intéressées devrait un const.

Ainsi, pour résoudre ce dilemme, ce que je ferais:

Débarrassez-vous des fonctions OnXXXXUpdated de vos classes de gestionnaire.

Ecrire un gestionnaire de notification, voici un prototype, avec les hypothèses suivantes:

"args" est une base de classe arbitraire pour la transmission d'informations lorsque les notifications se produisent

"délégué" est une sorte de pointeur de fonction (par exemple FastDelegate).

class Args
{
};

class NotificationManager
{
private:
    class NotifyEntry
    {
    private:
        std::list<Delegate> m_Delegates;

    public:
        NotifyEntry(){};
        void raise(const Args& _args) const
        {
            for(std::list<Delegate>::const_iterator cit(m_Delegates.begin());
                cit != m_Delegates.end();
                ++cit)
                (*cit)(_args);
        };

        NotifyEntry& operator += (Delegate _delegate) {m_Delegates.push_back(_delegate); return(*this); };
    }; // eo class NotifyEntry

    std::map<std::string, NotifyEntry*> m_Entries;

public:
    // ctor, dtor, etc....

    // methods
    void register(const std::string& _name);     // register a notification ...
    void unRegister(const std::string& _name);   // unregister it ...

    // Notify interested parties
    void notify(const std::string& _name, const Args& _args) const
    {
        std::map<std::string, NotifyEntry*>::const_iterator cit = m_Entries.find(_name);
        if(cit != m_Entries.end())
           cit.second->raise(_args);
    }; // eo notify

    // Tell the manager we're interested in an event
    void listenFor(const std::string& _name, Delegate _delegate)
    {
        std::map<std::string, NotifyEntry*>::const_iterator cit = m_Entries.find(_name);
        if(cit != m_Entries.end())
            (*cit.second) += _delegate;
    }; // eo listenFor
}; // eo class NotifyManager

Je l'ai laissé un peu de code comme vous pouvez le constater, mais vous voyez l'idée. Je suppose que ce gestionnaire de notification serait un singleton. Maintenant, assurer que le gestionnaire de notification est créée au début, le reste de vos gestionnaires tout simplement vous enregistrer leurs notifications dans leur constructeur comme ceci:

MyManager::MyManager()
{
    NotificationMananger.getSingleton().register("OnABCUpdated");
    NotificationMananger.getSingleton().register("OnXYZUpdated");
};


AnotherManager::AnotherManager()
{
    NotificationManager.getSingleton().register("TheFoxIsInTheHenHouse");
};

Maintenant, quand votre gestionnaire a besoin d'informer les intéressés, il suffit d'appels notification:

MyManager::someFunction()
{
    CustomArgs args; // custom arguments derived from Args
    NotificationManager::getSingleton().notify("OnABCUpdated", args);
};

D'autres classes peuvent écouter ce genre de choses.

Je me suis rendu compte que je viens dactylographié le motif d'observateur, mais mon intention était de montrer que le problème est dans la façon dont ces choses sont élevés et si elles sont dans un état const ou non. En abstraire le processus de notification de la classe mananager, destinataires de la notification sont libres de modifier cette classe de gestionnaire. Tout simplement pas le gestionnaire de notification. Je pense que cela est juste.

De plus, ayant un lieu unique aux notifications de relance est bonne pracice IMHO, car il vous donne un seul endroit où vous pouvez suivre vos notifications.

Je suppose que vous suivez HICPP ou quelque chose de similaire.

Ce que nous faisons est si notre code viole QACPP et nous pensons qu'il est dans l'erreur alors nous notons via Doxygen (via la commande AjouterÀGroupe de sorte que vous obtenez une liste d'entre eux facilement), donner une raison jusifying pourquoi nous enfreignons, puis désactiver l'avertissement via la commande //PRQA.

  

Notez qu'ils ne modifient pas l'état   de cet objet, afin qu'ils puissent   être fait techniquement des méthodes de const,   même si elles modifient généralement la   état du système dans son ensemble. Dans   en particulier, l'auditeur des objets pourrait   rappellerons dans cet objet et modifier   il.

Depuis l'auditeur peut changer un état, cette méthode ne doit pas être const. D'après ce que vous avez écrit, il semble que vous utilisez beaucoup de const_cast et appel par des pointeurs.

correct const a (volontairement souhaitable) façon de propager. vous devez utiliser const où vous pouvez vous en sortir avec elle, tandis que const_cast et c-style-moulages devraient être des artefacts de traiter avec le code client -. jamais dans votre code, mais exceptions très rares

si les appels void NotifyFooUpdated(); listeners[all].OnFooUpdated() pendant OnFooUpdated() n'est pas const, alors vous devriez qualifier explicitement cette mutation. si votre code est correct tout au long const (que je me demande), puis le rendre explicite (par déclaration de méthode / accès auditeur) que vous êtes les auditeurs (muter membres), alors NotifyFooUpdated() doit être considérée comme non-const. , vous déclarez que la mutation aussi proche de la source que possible et il doit vérifier et const-exactitude propagera correctement.

Faire des fonctions virtuelles est const toujours une décision difficile. Ce qui les rend non-const est hors de façon simple. Une fonction d'écoute doit être const dans de nombreux cas: si elle ne change pas l'aspect d'écoute (pour cet objet). Si l'écoute d'un événement causerait la partie d'écoute désinscription lui-même (comme un cas général), cette fonction doit être non-const.

Bien que l'état interne de l'objet peut changer sur un appel OnFooChanged, au niveau de l'interface, la prochaine fois que OnFooChanged est appelée, aura un résultat similaire. Ce qui le rend const.

Lorsque vous utilisez const dans vos classes que vous aidez les utilisateurs de ce savoir classe comment la classe interagir avec les données. Vous faites un contrat. Lorsque vous avez une référence à un objet const, vous savez que tous les appels effectués sur cet objet ne changera pas son état. Le constness de cette référence est encore seulement un contrat avec l'appelant. L'objet est toujours libre de faire certaines actions non-const en arrière-plan en utilisant des variables mutables. Ceci est particulièrement utile lorsque l'information mise en cache.

Par exemple, vous pouvez avoir la classe avec la méthode:

int expensiveOperation() const
{
    if (!mPerformedFetch)
    {
        mValueCache = fetchExpensiveValue();
        mPerformedFetch = true;
    }
    return mValueCache;
}

Cette méthode peut prendre beaucoup de temps pour effectuer la première fois, mais cache le résultat pour les appels suivants. Vous ne devez vous assurer que le fichier d'en-tête déclare les variables performedFetch et valueCache mutable.

class X
{
public:
    int expensiveOperation() const;
private:
    int fetchExpensiveValue() const;

    mutable bool mPerformedFetch;
    mutable int mValueCache;
};

Ceci permet à un objet const faire le contrat avec l'appelant, et d'agir comme il est const tout en travaillant un peu plus intelligent en arrière-plan.

Je suggère de faire votre classe déclarer la liste des auditeurs mutable, et tout faire d'autre const que possible. En ce qui concerne l'appelant de l'objet est concerné, l'objet est encore const et d'agir de cette façon.

Const signifie l'état de l'objet n'a pas été modifié par la fonction membre , ni plus, ni moins. Il n'a rien à voir avec des effets secondaires. Donc, si je comprends bien votre cas correctement, l'état de l'objet ne change pas cela signifie que la fonction doit être déclarée const, l'état des autres parties de votre application n'a rien à voir avec cet objet. Même lorsque parfois l'état de l'objet a des sous-objets non-const, qui ne font pas partie de l'état logique de l'objet (par exemple mutex), les fonctions doivent encore être réalisés const, et ces pièces doivent être déclarés mutable.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top