tipo tener punteros puntero objeto expresion estructuras estructura ejemplos debe datos curso clases apuntadores c++ language-lawyer protected access-specifier member-pointers

c++ - tener - Acceso al miembro protegido a través del puntero de miembro: ¿es un truco?



punteros c++ ejemplos (4)

es un hack?

De manera similar al uso de reinterpret_cast , esto puede ser peligroso y potencialmente puede ser una fuente de errores difíciles de encontrar. Pero está bien formado y no hay duda de si debería funcionar.

Para aclarar la analogía: El comportamiento de reinterpret_cast también se especifica exactamente en el estándar y se puede usar sin ningún UB. Pero reinterpret_cast evita el sistema de tipos, y el sistema de tipos está ahí por una razón. De manera similar, este truco de puntero a miembro está bien formado de acuerdo con el estándar, pero evita la encapsulación de miembros, y esa encapsulación (típicamente) existe por una razón (lo digo típicamente, ya que supongo que un programador puede usar la encapsulación frívolamente).

[¿Es] un problema técnico en algún lugar de la implementación o la redacción del Estándar?

No, la implementación es correcta. Así es como se ha especificado el idioma para trabajar.

La función miembro de Derived obviamente puede acceder a &Derived::value , ya que es un miembro protegido de una base.

El resultado de esa operación es un puntero a un miembro de Base . Esto se puede aplicar a una referencia a Base . Los privilegios de acceso de los miembros no se aplican a los punteros a los miembros: se aplican solo a los nombres de los miembros.

De los comentarios surgió otra pregunta: si Derived :: f se llama con una Base real, ¿es un comportamiento indefinido?

No UB Base tiene el miembro.

Todos sabemos que a los miembros especificados protected de una clase base solo se puede acceder desde una propia instancia de clase derivada. Esta es una característica del Estándar, y esto se ha discutido en Stack Overflow varias veces:

  • No se puede acceder al miembro protegido de otra instancia desde el alcance del tipo derivado ;
  • ¿Por qué mi objeto no puede acceder a miembros protegidos de otro objeto definido en la clase base común?
  • Y otros.

Pero parece posible sortear esta restricción con punteros de miembros, como me ha demostrado el usuario chtz:

struct Base { protected: int value; }; struct Derived : Base { void f(Base const& other) { //int n = other.value; // error: ''int Base::value'' is protected within this context int n = other.*(&Derived::value); // ok??? why? (void) n; } };

Demostración en vivo en coliru

¿Por qué es esto posible, es una característica deseada o un problema técnico en algún lugar de la implementación o la redacción del Estándar?

De los comentarios surgió otra pregunta: si Derived::f se llama con una Base real , ¿es un comportamiento indefinido?


Básicamente lo que estás haciendo es engañar al compilador, y se supone que esto funciona. Siempre veo este tipo de preguntas y la gente a veces obtiene malos resultados y otras veces funciona, dependiendo de cómo se convierta en código ensamblador.

Recuerdo haber visto un caso con una palabra clave const en un entero, pero luego, con algunos trucos, el tipo pudo cambiar el valor y eludió con éxito la conciencia del compilador. El resultado fue: un valor incorrecto para una operación matemática simple. La razón es simple: el ensamblaje en x86 hace una distinción entre constantes y variables, porque algunas instrucciones contienen constantes en su código de operación. Entonces, dado que el compilador cree que es una constante, lo tratará como una constante y lo tratará de manera optimizada con la instrucción incorrecta de la CPU, y baam, tiene un error en el número resultante.

En otras palabras: el compilador intentará hacer cumplir todas las reglas que puede hacer cumplir, pero probablemente pueda engañarlo, y puede obtener o no resultados incorrectos en función de lo que está tratando de hacer, por lo que es mejor que haga tales cosas solo si sabes lo que estás haciendo.

En su caso, el puntero &Derived::value se pueden calcular a partir de un objeto por la cantidad de bytes que hay desde el comienzo de la clase. Esto es básicamente cómo accede el compilador, entonces, el compilador:

  1. No ve ningún problema con los permisos, porque está accediendo al value derived en tiempo de compilación.
  2. Puede hacerlo, porque está tomando el desplazamiento en bytes en un objeto que tiene la misma estructura que la derived (bueno, obviamente, la base ).

Entonces, no estás violando ninguna regla. Has eludido con éxito las reglas de compilación. No debe hacerlo, exactamente por las razones descritas en los enlaces que adjuntó, ya que rompe la encapsulación OOP, pero, bueno, si sabe lo que está haciendo ...


El hecho de que un miembro no sea accesible usando el acceso de miembro de clase expr.ref ( aclass.amember ) debido al control de acceso [class.access] no hace que este miembro sea inaccesible usando otras expresiones.

La expresión &Derived::value (cuyo tipo es int Base::* ) cumple perfectamente con el estándar y designa el value del miembro de Base . Entonces, la expresión a_base.*p donde p es un puntero a un miembro de Base y a_base una instancia de Base también es estándar .

Por lo tanto, cualquier compilador compatible estándar hará que la expresión sea other.*(&Derived::value); comportamiento definido: acceder al value del miembro de other .


Solo para agregar a las respuestas y ampliar un poco el horror que puedo leer entre líneas. Si ve a los especificadores de acceso como ''la ley'', que lo controla para evitar que haga ''cosas malas'', creo que se está perdiendo el punto. public , protected , private , const ... son parte de un sistema que es una gran ventaja para C ++. Los idiomas sin él pueden tener muchos méritos, pero cuando construye sistemas grandes, tales cosas son un activo real.

Dicho esto: creo que es bueno que sea posible sortear casi todas las redes de seguridad que se le proporcionan. Siempre y cuando recuerde que "posible" no significa "bueno". Es por eso que nunca debería ser ''fácil''. Pero por lo demás, depende de usted. Eres el arquitecto

Hace años, simplemente podía hacer esto (y aún podría funcionar en ciertos entornos):

#define private public

Muy útil para archivos de encabezado externos ''hostiles''. ¿Buena práctica? ¿Qué piensas? Pero a veces tus opciones son limitadas.

Entonces, sí, lo que muestra es una especie de brecha en el sistema. Pero oye, ¿qué te impide derivar y entregar referencias públicas al miembro? Si los horribles problemas de mantenimiento lo encienden, por todos los medios, ¿por qué no?