initialize cplusplus c++ class pointers

cplusplus - static function c++



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

Me encontré con 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áticos de una clase? ¿Cuál es el uso de este puntero extraño en código real?


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

#include <iostream> #include <list> #include <string> template <typename Container, typename T, typename DataPtr> 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<Object*> 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 }


Creo que solo querría hacer esto si los datos de los miembros eran bastante grandes (por ejemplo, un objeto de otra clase bastante fuerte), y tiene alguna rutina externa que solo funciona con referencias a objetos de esa clase. No desea copiar el objeto miembro, así que esto le permite pasarlo.


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é querría hacer eso, bueno, le da otro nivel de direccionamiento indirecto que puede resolver algunos problemas difíciles. Pero para ser honesto, nunca tuve que usarlos en mi propio código.

Edición: no puedo dejar de lado un uso convincente para los punteros a los datos de los miembros. El puntero a las funciones miembro se puede usar en arquitecturas conectables, pero una vez más, producir un ejemplo en un espacio pequeño me derrota. Lo 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 de la función c->*func son necesarios porque el operador ->* tiene una prioridad más baja que el operador de llamada a la función.


Este es el ejemplo más simple que se me ocurre que transmite los casos raros 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 que se pasa a count_fruit. Esto le ahorra tener que escribir funciones count_apples y count_oranges separadas.


Este es un ejemplo del mundo real en el que estoy trabajando ahora, desde 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 supongamos que los metes en un vector:

std::vector<Sample> samples; ... fill the vector ...

Ahora suponga que desea calcular alguna función (diga la media) de una de las variables en un rango de muestras, y desea factorizar este cálculo de 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 Editado el 2016/08/05 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 adición consigo misma y la división por tamaño_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

Debería notar, 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 si está calculando una FFT, etc., debería almacenar los valores de cada variable de forma contigua en la memoria. De lo contrario, la iteración de la serie causará una falta de caché por 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é. Por lo tanto, en cada iteración del bucle, se extraerá una muestra de la memoria a la memoria caché. Se utilizarán 4 bytes de la línea de caché y el resto se tirará, y la siguiente iteración dará como resultado otra falta 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 carga desde la memoria, los tres siguientes también se cargarán en el caché (suponiendo una alineación adecuada), lo que significa que no es necesario 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 funcionan mucho mejor si todos los valores son 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.


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, proporciona un desplazamiento a 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, 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<<ob1.*data; cout<<ob2.*data; cout<<(ob1.*func)(ob1.*data); cout<<(ob2.*func)(ob2.*data); return 0; }

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


Más tarde puede acceder a este miembro, en cualquier caso:

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 activarlo, 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.


Otra aplicación son las listas intrusivas. El tipo de elemento puede indicar a la lista cuáles son sus punteros previos / siguientes. Por lo tanto, la lista no utiliza nombres codificados, pero puede seguir utilizando los 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); }


Permite vincular las variables y funciones de los miembros de manera uniforme. El siguiente es un ejemplo con su clase de coches. El uso más común sería vincular 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; }


Puede usar una matriz de punteros para datos de miembros (homogéneos) para habilitar una interfaz dual, con nombre de miembro (iexdata) y matriz-subíndice (es decir, x [idx]).

#include <cassert> #include <cstddef> 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(v.x == 1.0f); assert(v.y == 2.0f); assert(v.z == 3.0f); return 0; }



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

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

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

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

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:

void readDataFromText(std::istream & input, std::map<std::string, foo> & 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<std::string, foo> readValues() { std::map<std::string, foo> 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, llamar a readValues() devolverá un contenedor con un unísono de "input-a" y "input-b"; Todas las teclas estarán presentes, y los foos con a o b o ambos.


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

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 en la práctica si sientes que el código se está martillando lo suficiente como para que la sentencia if desacelere las cosas, por ejemplo. profundo en las entrañas de algún algoritmo intensivo en algún lugar. 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 eso es solo mi opinión.


IBM tiene algo más de 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 hacen referencia, así que:

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

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 deserializa fueron creados por un usuario de su biblioteca). Los punteros de los miembros le brindan una forma genérica y semi legible de referirse a las compensaciones de los miembros de datos individuales, sin tener que recurrir a trucos anulados * sin tipo de la forma en que podría hacerlo con las estructuras de C.