Me encontré con este extraño fragmento de código que se compila bien:
class Car
{
public:
int speed;
};
int main()
{
int Car::*pSpeed = &Car::speed;
return 0;
}
¿ Por qué C ++ tiene este puntero a un miembro de datos no estático de una clase? ¿De qué sirve este extraño puntero en código real?
Respuestas:
Es un "puntero al miembro": el siguiente código ilustra su uso:
En cuanto a por qué querrías hacer eso, bueno, te da otro nivel de indirección que puede resolver algunos problemas difíciles. Pero para ser honesto, nunca he tenido que usarlos en mi propio código.
Editar: No puedo pensar de manera casual en un uso convincente para los punteros a los datos de miembros. Las funciones de puntero a miembro se pueden usar en arquitecturas conectables, pero una vez más, producir un ejemplo en un espacio pequeño me derrota. El siguiente es mi mejor intento (no probado): una función Apply que realizaría un procesamiento previo y posterior antes de aplicar una función miembro seleccionada por el usuario a un objeto:
Los paréntesis
c->*func
son necesarios porque el->*
operador tiene menor prioridad que el operador de llamada de función.fuente
Este es el ejemplo más simple en el que puedo pensar que transmite los casos raros en los que esta característica es pertinente:
Lo que hay que tener en cuenta aquí es el puntero pasado a count_fruit. Esto le ahorra tener que escribir funciones separadas count_apples y count_oranges.
fuente
&bowls.apples
y&bowls.oranges
?&bowl::apples
y&bowl::oranges
no apunta a nada.&bowl::apples
y&bowl::oranges
no apunte a miembros de un objeto ; señalan a los miembros de una clase . Deben combinarse con un puntero a un objeto real antes de señalar algo. Esa combinación se logra con el->*
operador.Otra aplicación son las listas intrusivas. El tipo de elemento puede decirle a la lista cuáles son sus punteros siguiente / anterior. Por lo tanto, la lista no usa nombres codificados pero aún puede usar punteros existentes:
fuente
next
.Aquí hay un ejemplo del mundo real en el que estoy trabajando ahora, de los sistemas de procesamiento / control de señales:
Suponga que tiene alguna estructura que representa los datos que está recopilando:
Ahora suponga que los mete en un vector:
Ahora suponga que desea calcular alguna función (por ejemplo, la media) de una de las variables en un rango de muestras, y desea factorizar este cálculo medio en una función. El puntero a miembro lo hace fácil:
Nota editada el 2016/08/05 para un enfoque de plantilla-función más conciso
Y, por supuesto, puede crear una plantilla para calcular una media para cualquier iterador directo y cualquier tipo de valor que admita la adición consigo mismo y la división por size_t:
EDITAR: el código anterior tiene implicaciones de rendimiento
Debería tener en cuenta, como descubrí pronto, que el código anterior tiene algunas implicaciones de rendimiento graves. El resumen es que si está calculando una estadística de resumen en una serie de tiempo, o calculando una FFT, etc., entonces debe almacenar los valores para cada variable de forma contigua en la memoria. De lo contrario, iterar sobre la serie provocará una pérdida de caché por cada valor recuperado.
Considere el rendimiento de este código:
En muchas arquitecturas, una instancia de
Sample
llenará una línea de caché. Entonces, en cada iteración del bucle, una muestra se extraerá de la memoria al caché. Se usarán 4 bytes de la línea de caché y el resto se descartará, y la próxima iteración dará como resultado otra pérdida de caché, acceso a memoria, etc.Mucho mejor hacer esto:
Ahora, cuando el primer valor x se carga desde la memoria, los siguientes tres también se cargarán en la memoria caché (suponiendo una alineación adecuada), lo que significa que no necesita cargar ningún valor para las próximas tres iteraciones.
El algoritmo anterior se puede mejorar un poco más mediante el uso de instrucciones SIMD en, por ejemplo, arquitecturas SSE2. Sin embargo, estos funcionan mucho mejor si los valores están todos contiguos en la memoria y puede usar una sola instrucción para cargar cuatro muestras juntas (más en versiones SSE posteriores).
YMMV: diseñe sus estructuras de datos para adaptarlas a su algoritmo.
fuente
double Sample::*
parte es la clave!Más tarde puede acceder a este miembro, en cualquier caso:
Tenga en cuenta que necesita una instancia para llamarlo, por lo que no funciona como un delegado.
Se usa raramente, lo he necesitado tal vez una o dos veces en todos mis años.
Normalmente, usar una interfaz (es decir, una clase base pura en C ++) es la mejor opción de diseño.
fuente
IBM tiene más documentación sobre cómo usar esto. Brevemente, está utilizando el puntero como un desplazamiento en la clase. No puede usar estos punteros aparte de la clase a la que se refieren, así que:
Parece un poco oscuro, pero una posible aplicación es si está intentando escribir código para deserializar datos genéricos en muchos tipos de objetos diferentes, y su código necesita manejar tipos de objetos de los que no sabe absolutamente nada (por ejemplo, su código es en una biblioteca, y los objetos en los que se deserializan fueron creados por un usuario de su biblioteca). Los punteros de miembros le brindan una forma genérica y semi-legible de referirse a las compensaciones de miembros de datos individuales, sin tener que recurrir a trucos void * sin tipo de la forma en que lo haría para estructuras de C.
fuente
Permite unir variables miembro y funciones de manera uniforme. El siguiente es un ejemplo con su clase de automóvil. El uso más común sería vinculante
std::pair::first
y::second
cuando se usa en algoritmos STL y Boost en un mapa.fuente
Puede usar una matriz de puntero a datos de miembros (homogéneos) para habilitar una interfaz dual, con nombre de miembro (iexdata) y de subíndice de matriz (es decir, x [idx]).
fuente
union
juego de palabras para escribir de esa manera no está permitido por el estándar, ya que invoca numerosas formas de comportamiento indefinido ... mientras que esta respuesta está bien.float *component[] = { &x, &y, &z }; return *component[idx];
es decir, el puntero a componente parece no tener otro propósito que la ofuscación.Una forma en que lo he usado es si tengo dos implementaciones de cómo hacer algo en una clase y quiero elegir una en tiempo de ejecución sin tener que pasar continuamente por una instrucción if, es decir
Obviamente, esto solo es prácticamente útil si cree que el código se está manipulando lo suficiente como para que la instrucción if esté ralentizando las cosas, por ejemplo. profundamente en las entrañas de algún algoritmo intensivo en alguna parte. Todavía creo que es más elegante que la declaración if, incluso en situaciones en las que no tiene un uso práctico, pero esa es solo mi opinión.
fuente
Algorithm
y dos clases derivadas, por ejemplo,AlgorithmA
yAlgorithmB
. En tal caso, ambos algoritmos están bien separados y se garantiza que se prueben de forma independiente.Los punteros a las clases no son punteros reales ; una clase es una construcción lógica y no tiene existencia física en la memoria, sin embargo, cuando construye un puntero a un miembro de una clase, da un desplazamiento en un objeto de la clase del miembro donde se puede encontrar el miembro; Esto da una conclusión importante: dado que los miembros estáticos no están asociados con ningún objeto, un puntero a un miembro NO PUEDE apuntar a un miembro estático (datos o funciones) en absoluto. Considere lo siguiente:
Fuente: The Complete Reference C ++ - Herbert Schildt 4th Edition
fuente
Creo que solo querría hacer esto si los datos del miembro eran bastante grandes (por ejemplo, un objeto de otra clase bastante fuerte), y tiene alguna rutina externa que solo funciona en referencias a objetos de esa clase. No desea copiar el objeto miembro, por lo que le permite pasarlo.
fuente
Aquí hay un ejemplo donde el puntero a los miembros de datos podría ser útil:
fuente
Supongamos que tienes una estructura. Dentro de esa estructura hay * algún tipo de nombre * dos variables del mismo tipo pero con un significado diferente
Bien, ahora digamos que tienes un montón de
foo
s en un contenedor:Bien, ahora suponga que carga los datos de fuentes separadas, pero los datos se presentan de la misma manera (por ejemplo, necesita el mismo método de análisis).
Podrías hacer algo como esto:
En este punto, la llamada
readValues()
devolverá un contenedor con un unísono de "input-a" y "input-b"; todas las claves estarán presentes, y los foos con ya sea ao b o ambosfuente
Solo para agregar algunos casos de uso para la respuesta de @ anon's & @ Oktalist, aquí hay un excelente material de lectura sobre la función de puntero a miembro y los datos de puntero a miembro.
https://www.dre.vanderbilt.edu/~schmidt/PDF/C++-ptmf4.pdf
fuente