
Ashwin Nanjappa
Encontré este extraño fragmento de código que 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? Qué Cuál es el uso de este extraño puntero en código real?
Es un “puntero a miembro”; el siguiente código ilustra su uso:
#include <iostream>
using namespace std;
class Car
{
public:
int speed;
};
int main()
{
int Car::*pSpeed = &Car::speed;
Car c1;
c1.speed = 1; // direct access
cout << "speed is " << c1.speed << endl;
c1.*pSpeed = 2; // access via pointer to member
cout << "speed is " << c1.speed << endl;
return 0;
}
En cuanto a por qué le gustaría hacer eso, bueno, le da otro nivel de indirección que puede resolver algunos problemas complicados. Pero para ser honesto, nunca tuve que usarlos en mi propio código.
Editar: No puedo pensar en un uso convincente para los punteros a los datos de los 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 Aplicar que haría un procesamiento previo y posterior antes de aplicar una función miembro seleccionada por el usuario a un objeto:
void Apply( SomeClass * c, void (SomeClass::*func)() ) {
// do hefty pre-call processing
(c->*func)(); // call user specified function
// do hefty post-call processing
}
Los paréntesis alrededor c->*func
son necesarios porque la ->*
El operador tiene menor precedencia que el operador de llamada de función.
Este es el ejemplo más simple que se me ocurre que transmite los raros casos en los que esta característica es pertinente:
#include <iostream>
class bowl {
public:
int apples;
int oranges;
};
int count_fruit(bowl * begin, bowl * end, int bowl::*fruit)
{
int count = 0;
for (bowl * iterator = begin; iterator != end; ++ iterator)
count += iterator->*fruit;
return count;
}
int main()
{
bowl bowls[2] = {
{ 1, 2 },
{ 3, 5 }
};
std::cout << "I have " << count_fruit(bowls, bowls + 2, & bowl::apples) << " apples\n";
std::cout << "I have " << count_fruit(bowls, bowls + 2, & bowl::oranges) << " oranges\n";
return 0;
}
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.

Johannes Schaub – litb
Otra aplicación son las listas intrusivas. El tipo de elemento puede decirle a la lista cuáles son sus punteros siguiente/anterior. Entonces, la lista no usa nombres codificados, pero aún puede usar punteros existentes:
// say this is some existing structure. And we want to use
// a list. We can tell it that the next pointer
// is apple::next.
struct apple {
int data;
apple * next;
};
// simple example of a minimal intrusive list. Could specify the
// member pointer as template argument too, if we wanted:
// template<typename E, E *E::*next_ptr>
template<typename E>
struct List {
List(E *E::*next_ptr):head(0), next_ptr(next_ptr) { }
void add(E &e) {
// access its next pointer by the member pointer
e.*next_ptr = head;
head = &e;
}
E * head;
E *E::*next_ptr;
};
int main() {
List<apple> lst(&apple::next);
apple a;
lst.add(a);
}

Tomás
Aquí hay un ejemplo del mundo real en el que estoy trabajando ahora mismo, de los sistemas de control/procesamiento de señales:
Supongamos que tiene alguna estructura que representa los datos que está recopilando:
struct Sample {
time_t time;
double value1;
double value2;
double value3;
};
Ahora suponga que los mete en un vector:
std::vector<Sample> samples;
... fill the 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 de la media en una función. El puntero a miembro lo hace fácil:
double Mean(std::vector<Sample>::const_iterator begin,
std::vector<Sample>::const_iterator end,
double Sample::* var)
{
float mean = 0;
int samples = 0;
for(; begin != end; begin++) {
const Sample& s = *begin;
mean += s.*var;
samples++;
}
mean /= samples;
return mean;
}
...
double mean = Mean(samples.begin(), samples.end(), &Sample::value2);
Nota Editada el 05/08/2016 para un enfoque de función de plantilla 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 suma consigo mismo y la división por size_t:
template<typename Titer, typename S>
S mean(Titer begin, const Titer& end, S std::iterator_traits<Titer>::value_type::* var) {
using T = typename std::iterator_traits<Titer>::value_type;
S sum = 0;
size_t samples = 0;
for( ; begin != end ; ++begin ) {
const T& s = *begin;
sum += s.*var;
samples++;
}
return sum / samples;
}
struct Sample {
double x;
}
std::vector<Sample> samples { {1.0}, {2.0}, {3.0} };
double m = mean(samples.begin(), samples.end(), &Sample::x);
EDITAR: el código anterior tiene implicaciones de rendimiento
Debe tener en cuenta, como pronto descubrí, que el código anterior tiene algunas implicaciones serias en el rendimiento. El resumen es que si está calculando una estadística de resumen en una serie de tiempo, o calculando una FFT, etc., debe almacenar los valores de cada variable de forma contigua en la memoria. De lo contrario, iterar sobre la serie provocará una pérdida de memoria caché para cada valor recuperado.
Considere el rendimiento de este código:
struct Sample {
float w, x, y, z;
};
std::vector<Sample> series = ...;
float sum = 0;
int samples = 0;
for(auto it = series.begin(); it != series.end(); it++) {
sum += *it.x;
samples++;
}
float mean = sum / samples;
En muchas arquitecturas, una instancia de Sample
llenará una línea de caché. Entonces, en cada iteración del bucle, se extraerá una muestra 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 la memoria, etc.
Mucho mejor hacer esto:
struct Samples {
std::vector<float> w, x, y, z;
};
Samples series = ...;
float sum = 0;
float samples = 0;
for(auto it = series.x.begin(); it != series.x.end(); it++) {
sum += *it;
samples++;
}
float mean = sum / samples;
Ahora, cuando el primer valor de x se cargue de la memoria, los tres siguientes 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 siguientes 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 trabajos mucho mejor si los valores son todos contiguos en la memoria y puede usar una sola instrucción para cargar cuatro muestras juntas (más en versiones posteriores de SSE).
YMMV: diseñe sus estructuras de datos para adaptarse a su algoritmo.

peterchen
Más tarde podrá acceder a este miembro, en ninguna ejemplo:
int main()
{
int Car::*pSpeed = &Car::speed;
Car myCar;
Car yourCar;
int mySpeed = myCar.*pSpeed;
int yourSpeed = yourCar.*pSpeed;
assert(mySpeed > yourSpeed); // ;-)
return 0;
}
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.
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:
int Car::*pSpeed = &Car::speed;
Car mycar;
mycar.*pSpeed = 65;
Parece un poco oscuro, pero una posible aplicación es si está tratando de 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 deserializa 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 de vacío * sin tipo como lo haría con las estructuras C.
Hace posible vincular variables miembro y funciones de manera uniforme. El siguiente es un ejemplo con su clase de automóvil. Un uso más común sería vinculante std::pair::first
y ::second
cuando se usa en algoritmos STL y Boost en un mapa.
#include <list>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
class Car {
public:
Car(int s): speed(s) {}
void drive() {
std::cout << "Driving at " << speed << " km/h" << std::endl;
}
int speed;
};
int main() {
using namespace std;
using namespace boost::lambda;
list<Car> l;
l.push_back(Car(10));
l.push_back(Car(140));
l.push_back(Car(130));
l.push_back(Car(60));
// Speeding cars
list<Car> s;
// Binding a value to a member variable.
// Find all cars with speed over 60 km/h.
remove_copy_if(l.begin(), l.end(),
back_inserter(s),
bind(&Car::speed, _1) <= 60);
// Binding a value to a member function.
// Call a function on each car.
for_each(s.begin(), s.end(), bind(&Car::drive, _1));
return 0;
}
Aquí es donde lo encontré, también me confundió… pero ahora tiene sentido: stackoverflow.com/a/982941/211160
– HostileFork dice que no confíes en SE
12 de julio de 2012 a las 6:03
Los punteros a los miembros son la alternativa segura de tipos de C ++ a los bastante inseguros
offsetof()
construir desde C. Ambos devuelven la información, donde dentro de unclass
ostruct
se encuentra un determinado campo.– Kai Petzke
18 de febrero de 2021 a las 6:44