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:
-
No ve ningún problema con los permisos, porque está accediendo al
value
derived
en tiempo de compilación. -
Puede hacerlo, porque está tomando el desplazamiento en bytes en un objeto que tiene la misma estructura que la
derived
(bueno, obviamente, labase
).
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?