2010-10-19 16 views
13

estoy trabajando en algo de código C++ donde tengo varios objetos del gestor con métodos privados como¿Debo declarar estos métodos const?

void NotifyFooUpdated(); 

el que llama al método OnFooUpdated() de los oyentes de este objeto.

Tenga en cuenta que no modifican el estado de este objeto, por lo que técnicamente podrían hacerse métodos const, aunque normalmente modifican el estado del sistema como un todo. En particular, los objetos oyentes pueden devolver la llamada a este objeto y modificarlo.

Personalmente me gustaría dejarlos como están y no declararlos const.

Sin embargo, nuestro verificador de código estático QAC señala esto como una desviación, por lo que o bien tengo que declararlos const, o tengo que argumentar por qué deben permanecer sin const y obtener una concesión para la desviación.

¿Cuáles son los argumentos para no declarar estos métodos const?
¿O debería seguir QAC y declararlos const?
¿Debo adoptar un punto de vista estrictamente local restringido a este objeto o considerar el sistema como un todo?

Respuesta

3

Hablando en términos generales, tiene una clase de contenedor: un gerente lleno de observadores. En C y C++ puede tener contenedores const con valores no const. Considere si ha quitado una capa de envoltura:

list<Observer> someManager; 

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

Se podría ver nada extraño en un NotifyFooUpdated global que toma una lista const, ya que no modifica la lista. Ese argumento const realmente hace que el argumento de análisis sea más permisivo: la función acepta listas const y non-const. Toda la anotación const en la versión del método de clase significa const *this.

Para hacer frente a otro punto de vista:

Si no puede garantizar que el objeto que invoca la función en la que queda el mismo antes y después de la llamada a la función, se debe dejar en general que, como no constante.

Eso es razonable solo si la persona que llama tiene la única referencia al objeto. Si el objeto es global (como lo es en la pregunta original) o en un entorno con hebras, la constidad de cualquier llamada dada no garantiza que el estado del objeto no se modifique a lo largo de la llamada. Una función sin efectos secundarios y que siempre devuelve el mismo valor para las mismas entradas es pure. NotifyFooUpdate() claramente no es puro.

+0

Supongo que usted está argumentando a favor de declarar el método const? – starblue

+2

Si esto surgiera en una revisión del código, probablemente no me habría opuesto de ninguna manera a menos que hubiera algún otro factor en juego. Si todos acceden a lo global y no hay referencias, todo eso no importa. Estoy totalmente en desacuerdo con la premisa de que el análisis estático puede indicarle si este método debe ser const: El análisis estático le dice que * podría * ser const * tal como se implementó *, pero no sabe si tiene la intención de mejorar la función más tarde (p. Ej. con estadísticas, o puesta en cola de mensajes o eliminación de duplicados o lo que sea). –

0

Supongo que está siguiendo HICPP o algo similar.

Lo que hacemos es si nuestro código viola QACPP y creemos que es un error a continuación, observamos que a través de Doxygen (a través de AñadirAGrupo de comandos para que pueda obtener una lista de ellos con facilidad), dar una razón jusifying qué estamos violando y luego desactive la advertencia a través del comando //PRQA.

+0

Sí, técnicamente es similar aquí, la pregunta es qué hacer y con qué motivo. Además, no soy el que otorga desviaciones, por lo que necesito una comprensión lo suficientemente buena de los problemas para discutir con mi colega. – starblue

2

¿Cuáles son los argumentos para no declarar estos métodos const?
¿O debería seguir QAC y declararlos const?
¿Debo adoptar un punto de vista estrictamente local restringido a este objeto o considerar el sistema como un todo?

Lo que se sabe es que ese administrador de objetos esto fue llamada para hacer no cambio. Los objetos que el administrador invoca funciones en pueden cambiar o no. Tú no sabes eso.

De su descripción podría imaginar un diseño en el que todos los objetos implicados son const (y las notificaciones pueden procesarse escribiéndolas en la consola). Si no hace esta función const, lo prohíbe. Si lo hace const, permite ambos.

Supongo que este es un argumento a favor de hacer esto const.

+0

Creo que esto sería relativamente extraño, ya que todos los gerentes no son const. También tenga en cuenta que estos métodos son privados (he editado la pregunta), y por lo general los métodos que los llaman modifican el estado de los objetos. – starblue

3

Si los oyentes se almacenan como una colección de punteros, puede llamar a un método no const incluso si su objeto es const.

Si el contrato es que un oyente puede actualizar su estado cuando recibe una notificación, entonces el método debe ser no const.

Usted dice que el oyente puede devolver la llamada al objeto y modificarlo.Pero el oyente no se cambiará a sí mismo, por lo que la llamada de notificación podría ser const pero se pasa un puntero no const a su propio objeto en él.

Si el oyente ya tiene ese puntero (solo escucha una cosa), entonces puede hacer que los dos métodos sean const, ya que su objeto modificado es un efecto secundario. Lo que está sucediendo es:

A Las llamadas B B modifican A como resultado.

Así que una llamada B lleva indirectamente a su propia modificación, pero no es una modificación directa de uno mismo.

Si este es el caso, ambos métodos podrían y probablemente deberían ser const.

+0

Sí, técnicamente es posible hacer que el método sea const, pero ¿es también un buen "diseño"? ¿Quieres decir que siempre es bueno hacer las cosas lo más const posible? Por cierto, los oyentes se almacenan como punteros no const y generalmente se cambian ellos mismos. – starblue

1

Si no puede garantizar que el objeto invocado a la función sigue siendo el mismo antes y después de la llamada a la función, generalmente debe dejarlo como no const. Piénselo: podría escribir un oyente, insertarlo mientras el objeto no es const y luego usar esta función para violar la const correctness porque una vez tuvo acceso a ese objeto cuando no era const en el pasado. Eso está mal.

0

Tenga en cuenta que no modifiquen el estado de este objeto, para que pudieran hacerse técnicamente métodos const, a pesar de que normalmente modifican el estado del sistema en su conjunto. En en particular, los objetos oyentes pueden llamar a este objeto y modificarlo .

Como el oyente puede cambiar un estado, este método no debe ser const. Según lo que escribiste, parece que estás usando muchos const_cast y llamas a través de punteros.

+0

No, no hay 'const_cast' involucrado, los otros objetos tienen punteros no const para interactuar con este objeto. – starblue

+0

Mientras su método esté cambiando algo, no se debe declarar const. Simple como eso. –

0

const correctness tiene una forma (intencionalmente deseable) de propagación. debe usar const siempre que pueda salirse con la suya, mientras que const_cast y c-style-casts deberían ser artefactos de tratar con el código del cliente, nunca en su código, pero muy raras excepciones.

si void NotifyFooUpdated(); llamadas listeners[all].OnFooUpdated() mientras que en OnFooUpdated() no es constante, entonces usted debe calificar explícitamente esta mutación. si su código es constante en todo momento (lo cual estoy cuestionando), explíquelo (a través de la declaración del método/acceso del oyente) que está mutando a los oyentes (miembros), entonces NotifyFooUpdated() debería calificar como no const. entonces, solo declara la mutación lo más cerca posible de la fuente y debería verificar y la corrección de const se propagará correctamente.

0

Hacer funciones virtuales const es siempre una decisión difícil. Hacerlos no const es la salida más fácil. Una función de escucha debe ser const en muchos casos: si no cambia el aspecto de escucha (para este objeto). Si escuchar un evento provocaría que el grupo que escucha se desregistre a sí mismo (como un caso general), entonces esta función no debería ser const.

Aunque el estado interno del objeto puede cambiar en una llamada OnFooChanged, en el nivel de la interfaz, la próxima vez que se llame a OnFooChanged, tendrá un resultado similar. Lo que lo hace const.

0

Al usar const en sus clases, está ayudando a los usuarios de esa clase a saber cómo interactuará la clase con los datos. Usted está haciendo un contrato. Cuando tiene una referencia a un objeto const, sabe que las llamadas realizadas en ese objeto no cambiarán su estado. La consistencia de esa referencia sigue siendo solo un contrato con la persona que llama. El objeto aún es libre de hacer algunas acciones no const en el fondo usando variables mutables.Esto es particularmente útil cuando se almacena información en caché.

Por ejemplo, puede tener clase con el método:

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

Este método puede tardar mucho tiempo para realizar el primer tiempo, pero en caché el resultado de las llamadas posteriores. Solo necesita asegurarse de que el archivo de encabezado declare las variables realizadasFetch y valueCache como mutables.

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

    mutable bool mPerformedFetch; 
    mutable int mValueCache; 
}; 

Esto permite que un objeto constante hacen que el contrato con la persona que llama, y ​​actuar como es const mientras se trabaja un poco más inteligente en el fondo.

Sugeriría que su clase declare la lista de oyentes como mutable, y haga que todo lo demás sea lo más const posible. En lo que respecta a la persona que llama al objeto, el objeto todavía está const y actúa de esa manera.

1

Mi opinión es que deben permanecer no const. Esto se basa en mi percepción de que el estado del objeto administrador es en realidad el agregado de los estados de todos los objetos que gestiona más cualquier estado intrínseco, es decir, State(Manager) = State(Listener0) + State(Listener1) + ... + State(ListenerN) + IntrinsicState(Manager).

Si bien la encapsulación en el código fuente puede desmentir esta relación de tiempo de ejecución. Según su descripción, creo que este estado agregado refleja el comportamiento en tiempo de ejecución del programa.

Para reforzar mi argumento: afirmo que el código debe esforzarse por reflejar el comportamiento del tiempo de ejecución del programa en lugar de seguir estrictamente la semántica exacta de la compilación.

1

Paraconst, o noconst: esa es la cuestión.

Argumentos para const:

  • Los métodos en cuestión no modifican el estado del objeto.
  • Su comprobador de código estático señala la falta de const como una desviación, tal vez debería escucharla.

Argumentos contra const:

  • Los métodos de modificar el estado del sistema en su conjunto.
  • Los objetos del oyente modifican el objeto.

Personalmente me gustaría ir con dejarlo como const, el hecho de que podría modificar el estado del sistema en su conjunto es más o menos como una referencia de puntero nulo. Es un método const, no modifica el objeto en cuestión, pero se bloqueará el programa modificando el estado de todo el sistema.

1

hay algunos buenos argumentos para contra const, aquí, así que aquí está mi opinión: -

Personalmente, no tendríamos estos "OnXXXUpdated" como parte de mis clases de administrador. Creo que esta es la razón por la cual hay cierta confusión en cuanto a las mejores prácticas. Está notificando a las partes interesadas sobre algo y no sabe si el estado del objeto cambiará o no durante el proceso de notificación. Puede, o no puede. Lo que es obvio para mí, es que el proceso de notificación a las partes interesadas debe ser un const.

Por lo tanto, para resolver este dilema, esto es lo que haría:

deshacerse de las funciones OnXXXXUpdated de sus clases de administrador.

Escribe un Administrador de notificaciones, aquí hay un prototipo, con los siguientes supuestos:

"Args" es una clase base arbitraria para pasar información cuando las notificaciones suceden

"Delegado" es una especie de un puntero de función (p. ej. 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 

he dejado algo de código como usted puede decir probablemente, pero usted consigue la idea. Me imagino que este Administrador de notificaciones sería un singleton. Ahora, asegurando que el Administrador de notificaciones se crea desde el principio, el resto de sus gerentes simplemente registrar sus notificaciones en su constructor de la siguiente manera:

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


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

Ahora, cuando el gerente tiene que notificar a las partes interesadas, simplemente llamadas notificar:

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

Otras clases pueden escuchar esto.

Me he dado cuenta de que acabo de tipear el patrón Observer, pero mi intención era mostrar que el problema está en cómo se plantean estas cosas y si están en estado const o no. Al abstraer el proceso de notificación de la clase mananager, los destinatarios de la notificación son libres de modificar esa clase de administrador. Simplemente no el administrador de notificaciones. Creo que esto es justo.

Además, tener un solo lugar para levantar notificaciones es una buena práctica, ya que le da un lugar único donde puede rastrear sus notificaciones.

0

Const significa que el estado del objeto no es modificado por la función de miembro, ni más ni menos. No tiene nada que ver con los efectos secundarios. Entonces, si entiendo su caso correctamente, el estado del objeto no cambia, lo que significa que la función debe declararse const, el estado de las otras partes de su aplicación no tiene nada que ver con este objeto. Incluso cuando a veces el estado del objeto tiene subobjetos no const, que no son parte del estado lógico del objeto (por ejemplo, mutexes), las funciones todavía tienen que hacerse const, y esas partes deben declararse mutables.

Cuestiones relacionadas