Puntero al miembro de datos de clase “::*”

13 minutos de lectura

Puntero al miembro de datos de clase
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?

  • 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 un class o struct se encuentra un determinado campo.

    – Kai Petzke

    18 de febrero de 2021 a las 6:44

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.

  • ¿Podría mostrar un ejemplo de una situación complicada en la que esto sea útil? Gracias.

    –Ashwin Nanjappa

    22 de marzo de 2009 a las 9:31

  • Tengo un ejemplo del uso de puntero a miembro en una clase de Rasgos en otra respuesta SO.

    – Mike DeSimone

    13 de abril de 2011 a las 19:08

  • Un ejemplo es escribir una clase de tipo “devolución de llamada” para algún sistema basado en eventos. El sistema de suscripción de eventos de la interfaz de usuario de CEGUI, por ejemplo, toma una devolución de llamada con plantilla que almacena un puntero a una función miembro de su elección, para que pueda especificar un método para manejar el evento.

    – Benji XVI

    28 de diciembre de 2012 a las 21:03


  • Hay un ejemplo muy bueno de puntero adatos-uso de miembros en una función de plantilla en este codigo

    – alveco

    6 de junio de 2013 a las 22:43


  • Recientemente he usado punteros a miembros de datos en el marco de serialización. El objeto de marshaller estático se inicializó con una lista de contenedores que contenían un puntero a miembros de datos serializables. Un prototipo temprano de este código.

    – Alexei Biryukov

    08/04/2015 a las 22:25

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.

  • ¿No debería ser &bowls.apples y &bowls.oranges? &bowl::apples y &bowl::oranges no apunta a nada.

    – Dan Nissenbaum

    30 de marzo de 2014 a las 9:20

  • &bowl::apples y &bowl::oranges no señale a los miembros de una objeto; señalan a los miembros de una clase. Deben combinarse con un puntero a un objeto real antes de que apunten a algo. Esa combinación se logra con la ->* operador.

    – John MacFarlane

    30/03/2014 a las 20:56

  • ¡Muchas gracias por este ejemplo tan ilustrativo! Sin embargo, creo que todavía no entiendo completamente la expresión: int bowl::*fruit. ¿Cuál es el tipo y cuál es el nombre del parámetro de esta expresión?

    – Fabian

    12 abr 2021 a las 17:37

  • @fabian YW! El nombre del parámetro es fruit. Su tipo dice: “Señalo una int ese es un miembro de la bowl clase”. Bajo el capó, normalmente se implementa como un desplazamiento desde el inicio de la clase, es decir, 0 bytes para apples o 4 bytes para oranges. Aquí está un ejemplo más simple con una función que solo incrementa un miembro. fruit especifica donde en b esa variable miembro es como un desplazamiento de bytes. y aquí está el código de llamada que o bien pasa 0 o 4 en como ese desplazamiento.

    – John MacFarlane

    12 abr 2021 a las 20:53


1647578591 708 Puntero al miembro de datos de clase
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);
}

  • Si esta es realmente una lista enlazada, ¿no le gustaría algo como esto: void add(E* e) { e->*next_ptr = head; cabeza = e; } ??

    – eeeaaii

    25 de agosto de 2011 a las 16:56


  • @eee, te recomiendo que leas sobre los parámetros de referencia. Lo que hice es básicamente equivalente a lo que hiciste tú.

    – Johannes Schaub – litb

    25 de agosto de 2011 a las 18:55


  • +1 para su ejemplo de código, pero no vi ninguna necesidad para el uso de puntero a miembro, ¿algún otro ejemplo?

    – Alcott

    14 de agosto de 2012 a las 8:32

  • @Alcott: puede aplicarlo a otras estructuras similares a listas vinculadas donde el siguiente puntero no se nombra next.

    – icktoofay

    18 mayo 2013 a las 23:13

Puntero al miembro de datos de clase
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.

1647578591 53 Puntero al miembro de datos de clase
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.

  • ¿Pero seguramente esto es solo una mala práctica? debería hacer algo como youcar.setspeed(mycar.getpspeed)

    – thecoshman

    6 oct 2010 a las 21:08

  • @thecoshman: depende completamente: ocultar miembros de datos detrás de métodos set/get no es encapsulación y simplemente un intento de lecheras de abstracción de interfaz. En muchos escenarios, la “desnormalización” de los miembros públicos es una opción razonable. Pero esa discusión probablemente excede los límites de la funcionalidad de comentarios.

    – peterchen

    12 de octubre de 2010 a las 15:21

  • +1 por señalar, si entiendo correctamente, que este es un puntero a un miembro de cualquier instancia, y no un puntero a un valor específico de una instancia, que es la parte que me faltaba por completo.

    – johnbakers

    21 de mayo de 2013 a las 9:49

  • @Fellowshee Lo entiendes correctamente 🙂 (enfatizó eso en la respuesta).

    – peterchen

    22 de mayo de 2013 a las 16:58

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.

  • ¿Pero seguramente esto es solo una mala práctica? debería hacer algo como youcar.setspeed(mycar.getpspeed)

    – thecoshman

    6 oct 2010 a las 21:08

  • @thecoshman: depende completamente: ocultar miembros de datos detrás de métodos set/get no es encapsulación y simplemente un intento de lecheras de abstracción de interfaz. En muchos escenarios, la “desnormalización” de los miembros públicos es una opción razonable. Pero esa discusión probablemente excede los límites de la funcionalidad de comentarios.

    – peterchen

    12 de octubre de 2010 a las 15:21

  • +1 por señalar, si entiendo correctamente, que este es un puntero a un miembro de cualquier instancia, y no un puntero a un valor específico de una instancia, que es la parte que me faltaba por completo.

    – johnbakers

    21 de mayo de 2013 a las 9:49

  • @Fellowshee Lo entiendes correctamente 🙂 (enfatizó eso en la respuesta).

    – peterchen

    22 de mayo de 2013 a las 16:58

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;
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad