C ++: puntero al miembro de datos de clase “:: *”

Encontré este extraño fragmento de código que comstack 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? ¿Cuál es el uso de este puntero extraño en código real?

Es un “puntero al miembro”: el siguiente código ilustra su uso:

 #include  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é querrías hacer eso, bien te 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 improvisadamente en un uso convincente para los punteros a los datos de los miembros. Las funciones de puntero a miembro se pueden usar en architectures conectables, pero una vez más producir un ejemplo en un espacio pequeño me derrota. La siguiente es mi mejor prueba (no probada): una función Aplicar que haría un poco de 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 de c->*func son necesarios porque el operador ->* tiene una prioridad menor que el operador de llamada a función.

Este es el ejemplo más simple que puedo pensar que transmite los raros casos en los que esta característica es pertinente:

 #include  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 notar aquí es el puntero pasado a count_fruit. Esto le ahorra tener que escribir funciones count_apples y count_oranges por separado.

Otra aplicación son listas intrusivas. El tipo de elemento puede indicar 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:

 // 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 template 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 lst(&apple::next); apple a; lst.add(a); } 

Luego puede acceder a este miembro en cualquier instancia:

 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 invocarlo, 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, la mejor opción de diseño es utilizar una interfaz (es decir, una clase base pura en C ++).

Aquí hay un ejemplo del mundo real en el que estoy trabajando ahora, desde los sistemas de procesamiento / control de señales:

Supongamos que tiene una estructura que representa los datos que está recostackndo:

 struct Sample { time_t time; double value1; double value2; double value3; }; 

Ahora supongamos que los metes en un vector:

 std::vector 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 medio en una función. El puntero a miembro lo hace fácil:

 double Mean(std::vector::const_iterator begin, std::vector::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 Editado 2016/08/05 para un enfoque de función de plantilla más conciso

Y, por supuesto, puede moldearlo 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 tamaño_t:

 template S mean(Titer begin, const Titer& end, S std::iterator_traits::value_type::* var) { using T = typename std::iterator_traits::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 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 graves de rendimiento. El resumen es que si está calculando una estadística de resumen en una serie temporal, o calculando una FFT, etc., debe almacenar los valores de cada variable contiguamente en la memoria. De lo contrario, iterar sobre la serie causará una falta de caché para cada valor recuperado.

Considere el rendimiento de este código:

 struct Sample { float w, x, y, z; }; std::vector 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 architectures, una instancia de Sample llenará una línea de caché. Por lo tanto, en cada iteración del ciclo, se extraerá una muestra de la memoria al caché. Se usarán 4 bytes de la línea de caché y el rest se desecharán, y la siguiente iteración dará como resultado otra falta de memoria caché, acceso a memoria, etc.

Mucho mejor para hacer esto:

 struct Samples { std::vector 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 se cargue el primer valor x de la memoria, los tres siguientes también se cargarán en la memoria caché (suponiendo que la alineación sea adecuada), lo que significa que no necesita ningún valor cargado para las siguientes tres iteraciones.

El algoritmo anterior se puede mejorar un poco más mediante el uso de instrucciones SIMD, por ejemplo, en architectures SSE2. Sin embargo, estos funcionan 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 las posteriores versiones SSE).

YMMV: diseña tus estructuras de datos para adaptarlas a tu algoritmo.

IBM tiene más documentación sobre cómo usar esto. En resumen, está usando el puntero como un desplazamiento en la clase. No puede usar estos punteros aparte de la clase a la que se refieren, entonces:

  int Car::*pSpeed = &Car::speed; Car mycar; mycar.*pSpeed = 65; 

Parece un poco oscuro, pero una posible aplicación es si estás tratando de escribir código para deserializar datos generics en muchos tipos de objetos diferentes, y tu código necesita manejar tipos de objetos de los que no sabe absolutamente nada (por ejemplo, tu código es en una biblioteca, y los objetos en los que se deserializa fueron creados por un usuario de su biblioteca). Los punteros de miembro le brindan una forma genérica y semilegible de referirse a los desplazamientos de miembro de datos individuales, sin tener que recurrir a trucos de vacío sin tipo * de la forma en que lo haría para las estructuras en C.

Permite enlazar variables y funciones miembro de manera uniforme. El siguiente es un ejemplo con tu clase de auto. 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.

 #include  #include  #include  #include  #include  #include  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 l; l.push_back(Car(10)); l.push_back(Car(140)); l.push_back(Car(130)); l.push_back(Car(60)); // Speeding cars list 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; } 

Puede utilizar una matriz de puntero a datos de miembros (homogéneos) para habilitar una interfaz dual, named-member (iexdata) y array-subscript (ie x [idx]).

 #include  #include  struct vector3 { float x; float y; float z; float& operator[](std::size_t idx) { static float vector3::*component[3] = { &vector3::x, &vector3::y, &vector3::z }; return this->*component[idx]; } }; int main() { vector3 v = { 0.0f, 1.0f, 2.0f }; assert(&v[0] == &v.x); assert(&v[1] == &v.y); assert(&v[2] == &v.z); for (std::size_t i = 0; i < 3; ++i) { v[i] += 1.0f; } assert(vx == 1.0f); assert(vy == 2.0f); assert(vz == 3.0f); return 0; } 

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 sentencia if, es decir,

 class Algorithm { public: Algorithm() : m_impFn( &Algorithm::implementationA ) {} void frequentlyCalled() { // Avoid if ( using A ) else if ( using B ) type of thing (this->*m_impFn)(); } private: void implementationA() { /*...*/ } void implementationB() { /*...*/ } typedef void ( Algorithm::*IMP_FN ) (); IMP_FN m_impFn; }; 

Obviamente, esto solo es útil si crees que el código está siendo evaluado lo suficiente como para que la instrucción if reduzca la velocidad de las cosas, por ejemplo. profundo en las entrañas de algún algoritmo intensivo en alguna parte. Todavía creo que es más elegante que la statement if incluso en situaciones en las que no tiene uso práctico, pero esa es solo mi opción.

Creo que solo querrías hacer esto si los datos de los miembros eran bastante grandes (por ejemplo, un objeto de otra clase bastante fuerte), y tienes 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.

Aquí hay un ejemplo donde el puntero a los miembros de datos podría ser útil:

 #include  #include  #include  template  typename Container::value_type searchByDataMember (const Container& container, const T& t, DataPtr ptr) { for (const typename Container::value_type& x : container) { if (x->*ptr == t) return x; } return typename Container::value_type{}; } struct Object { int ID, value; std::string name; Object (int i, int v, const std::string& n) : ID(i), value(v), name(n) {} }; std::list objects { new Object(5,6,"Sam"), new Object(11,7,"Mark"), new Object(9,12,"Rob"), new Object(2,11,"Tom"), new Object(15,16,"John") }; int main() { const Object* object = searchByDataMember (objects, 11, &Object::value); std::cout << object->name << '\n'; // Tom } 

Supongamos que tienes una estructura. Dentro de esa estructura hay * algún tipo de nombre * dos variables del mismo tipo pero con diferente significado

 struct foo { std::string a; std::string b; }; 

De acuerdo, ahora digamos que tienes un montón de foo en un contenedor:

 // key: some sort of name, value: a foo instance std::map container; 

Bueno, ahora supongamos que carga los datos de fonts 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:

 void readDataFromText(std::istream & input, std::map & container, std::string foo::*storage) { std::string line, name, value; // while lines are successfully retrieved while (std::getline(input, line)) { std::stringstream linestr(line); if ( line.empty() ) { continue; } // retrieve name and value linestr >> name >> value; // store value into correct storage, whichever one is correct container[name].*storage = value; } } std::map readValues() { std::map foos; std::ifstream a("input-a"); readDataFromText(a, foos, &foo::a); std::ifstream b("input-b"); readDataFromText(b, foos, &foo::b); return foos; } 

En este punto, al llamar a readValues() devolverá un contenedor con un unísono de “input-a” y “input-b”; todas las claves estarán presentes, y los foos con a o b o ambos.

Solo para agregar algunos casos de uso para la respuesta de @anon y @ Oktalist, aquí hay un excelente material de lectura sobre la función de puntero a miembro y los datos de puntero a miembro. http://www.cs.wustl.edu/~schmidt/PDF/C++-ptmf4.pdf

Los punteros a las clases no son indicadores reales ; una clase es una construcción lógica y no tiene existencia física en la memoria, sin embargo, cuando construyes un puntero a un miembro de una clase, da una compensación en un objeto de la clase del miembro donde se puede encontrar el miembro; Esto proporciona una conclusión importante: dado que los miembros estáticos no están asociados con ningún objeto, por lo que un puntero a un miembro NO PUEDE apuntar a un miembro estático (datos o funciones) en absoluto. Considere lo siguiente:

 class x { public: int val; x(int i) { val=i;} int get_val(){return val;} int d_val(int i){return i+i;} }; int main() { int (x::*data)=&x::val; //pointer to data member int (x::*func)(int)=&x::d_val; //pointer to function member x ob1(1),ob2(2); cout< 

Fuente: The Complete Reference C ++ - Herbert Schildt 4ª edición