dev compiler c++ c++11

compiler - c++11 download



¿Cómo evitar llamar accidentalmente una función de mutación en un objeto no constante? (7)

Supongamos que tenemos un objeto Objeto de tipo myType, y nos gustaría pasarlo a la función Foo, que nos devuelve información valiosa sobre el objeto. la barra de función es donde se declara el objeto y desde donde se llama a Foo de esta forma:

void Bar () { myType obj; //default constructor string valuableInfo = Foo(obj); //do other stuff //... } //end of Bar()

Este fragmento de código, por supuesto, no dice mucho acerca de si Foo toma obj como referencia o como valor, y si Foo modifica o no el obj de alguna manera.

Por supuesto, si Foo toma obj como valor o como referencia constante, no tendremos ningún problema.

string Foo (const myType & input); //this is fine string Foo (myType input); //so is this

¡Pero no tenemos garantizado esto! La firma de la función podría muy bien ser

string Foo (myType & input); //asking for trouble!!

pero es muy incómodo verificar la firma de cada función a la que nos gustaría pasar obj, así que, ¿cómo podemos especificar que solo queremos pasar nuestro objeto a funciones que prometen no modificarlo?

por supuesto, un enfoque es declarar obj como const, pero el problema con este enfoque es que perdemos flexibilidad. ¿Qué pasa si queremos modificar obj en Bar () después de llamar a Foo (obj)?

void Bar () { const myType obj; //default constructor string valuableInfo = Foo(obj); //compiler will complain if Foo''s signature doesnt match //we can''t modify obj here :( //... } //end of Bar()

La solución obvia pero mala es hacer esto:

void Bar () { myType obj; //default constructor const myType immutableObj {obj}; //copy ctr call //this is expensive and not recommended if obj is big! want to avoid string valuableInfo = Foo(immutableObj); //will get the valuable Info risk free // compiler will complain if Foo has inappropriate signature //do other stuff //... } //end of Bar()

Entonces, ¿cuál es la mejor solución aquí? ¿Hay una manera de afirmar estáticamente que Foo no es invasivo al objeto que pasamos? ¿Podemos hacer temporalmente obj const (sin tener que crear un nuevo objeto const) o algo parecido?


Así que puede que las palabras ... Explicación simple, no hay garantía. He visto toneladas de código que toma un valor por referencia constante y que hace const constantes en él. Es principalmente prolifirato en funciones que toman un funtor con estado. Dado que el functor está en estado, se toma como referencia, pero como antes de rvalue references no se podía pasar un temporal a una función que acepta una referencia no constante, la firma es una referencia constante. Que el objeto es constante y el estado es alterado.


En C++17 , gracias a P0007R1 :

foo(std::as_const(obj));

Antes de C ++ 17, si necesita hacer esto a menudo, escribir un ayudante es trivial:

template<class T> constexpr typename std::add_const<T>::type& as_const(T& t) noexcept { return t; } // prevent accidentally creating an lvalue out of a const rvalue template<class T> void as_const(const T&&) = delete;

Por supuesto, nada de lo que hagas puede proteger contra alguien que desecha deliberadamente la constancia. Murphy, Maquiavelo, etc.


No puede garantizar nada con const, ni garantizar que el parámetro no se modifique de forma no deseada.

Foo() puede fácilmente const_cast<>() alejando la const del parámetro.


Puede lanzarlo al momento como lo sugirió Brian , pero también puede simplemente usar una referencia const :

myType obj; myType const &cObj = obj; string valuableInfo = Foo(cObj); mutate(obj);


Puedes usar un const_cast para hacerlo temporalmente const:

Foo(const_cast<const myType>(obj));


Voy a sugerir una solución indirecta.

Por supuesto, si Foo toma obj como valor o como referencia constante, no tendremos ningún problema.

string Foo (const myType & input); //this is fine

string Foo (myType input); // so is this

¡Pero no tenemos garantizado esto! La firma de la función podría muy bien ser

string Foo (myType & input); //asking for trouble!

Creo que hay algo más problemático aquí. Lo que no estamos viendo es la documentación de esta función de Foo : sus comentarios de interfaz, un nombre significativo, etc.

Lo primero que hay que entender acerca de esta función de Foo , incluso antes de usarla, son los efectos secundarios que tiene. Si no sabemos qué va a hacer con los argumentos que transmitimos sin una garantía de constness (que es solo una garantía débil como se señala y se debilita a const_casts que más const_casts introduce), entonces sugeriría que esto podría apuntar a un desglose en la forma en que Foo está documentado, sobrecargado o en la forma en que se utiliza.

Lo que sea que se llame Foo , ya sea rotate , display , clamp , lerp , paint , flip , info , etc., debe tener en claro sus efectos secundarios y no deben variar a un nivel lógico entre las sobrecargas. Las interfaces deben tener garantías aún más firmes con respecto a las invariantes que una constante con nombre sobre lo que harán y no harán.

Por ejemplo, si tiene un diseño de interfaz como este:

/// @return A flipped ''s'' (no side effects). Something flip(Something s); /// Flips ''s'' (one side effect). void flip(Something& s);

... este es un diseño extremadamente inductor de problemas: un cable trampa para todos los desarrolladores que lo usan, un nido / colmena de errores, ya que las sobrecargas varían enormemente en términos de sus efectos secundarios. Un diseño mucho menos confuso sería así:

/// @return A flipped ''s'' (no side effects). Something flipped(Something s); /// Flips ''s'' (one side effect). void flip(Something& s);

... uno que no sobrecarga el flip basado en efectos secundarios lógicos.

Si alguna vez se encuentra con un diseño como este y está fuera de su control, sugeriría envolverlo en algo más sensato, como introducir esa función flipped :

/// @return A flipped ''s'' (no side effects). Something flip(Something s); /// Flips ''s'' (one side effect). void flip(Something& s); /// @return A flipped ''s'' (no side effects). Something flipped(Something s) { flip(s); return s; }

... y en su lugar, utiliza esa función flipped en la que entiendes claramente sus efectos secundarios y lo que se supone que debe hacer y continuará haciéndolo independientemente de la mutabilidad de los argumentos que pases. Si bien esto es más const_cast que introducir un const_cast para invocar el la inmutable sobrecarga de la función, es conectar la fuente de confusión en la raíz en lugar de trabajar en torno a un diseño muy trippy al obligar a las cosas a pasar con constness .

constness se utiliza mejor como un mecanismo defensivo para los cambios potenciales que podrían ocurrir en el futuro, no para descubrir / imponer el comportamiento apropiado en el presente. Por supuesto, podría abordarlo con la justificación de garantizar que Foo(obj) no provocará efectos secundarios en el futuro (suponiendo que no lo haga en el presente), pero a nivel de interfaz, no debería haber inestabilidad. Con respecto a los efectos secundarios de este tipo. Si Foo(obj) no modifica el obj hoy, entonces definitivamente no debería hacerlo mañana. Por lo menos, una interfaz debe ser estable en ese sentido.

Imagine una base de código en la que llamar a abs(x) no lo dejara sintiéndose 100% seguro de si x sería modificado o no, o al menos no en el futuro. Ese no es el momento de buscar la constancia para resolver este problema: el problema aquí sería totalmente en el nivel de interfaz / diseño con respecto a los abs . No debería haber sobrecargas de parámetros mutables de abs que produzcan efectos secundarios. Nunca debería haber nada de este tipo, ni siquiera 10 años después, y eso debería ser una garantía firme en la que pueda confiar sin forzar sus argumentos a que los abs sean const . Debería poder tener un grado de confianza similar para cualquier función que use, siempre que sea incluso remotamente estable.

Entonces, aunque puede haber excepciones a la regla, sugeriría que verifique sus interfaces, asegúrese de que documenten las cosas correctamente, que no estén sobrecargadas de manera que produzcan efectos secundarios lógicos dispares en función de la sobrecarga que use, y que sean estables con respecto a lo que están documentados para hacer.


Foo(static_cast<const myType&>(obj));