Puntero al miembro de datos de clase ":: *"


242

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?


Aquí es donde lo encontré, también me confundió ... pero tiene sentido ahora: stackoverflow.com/a/982941/211160
HostileFork dice que no confíes en SE

Respuestas:


189

Es un "puntero al 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í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:

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 c->*funcson necesarios porque el ->*operador tiene menor prioridad que el operador de llamada de función.


3
¿Podría mostrar un ejemplo de una situación difícil en la que esto es útil? Gracias.
Ashwin Nanjappa

Tengo un ejemplo de uso de puntero a miembro en una clase de rasgos en otra respuesta SO .
Mike DeSimone

Un ejemplo es escribir una clase de tipo "devolución de llamada" para algún sistema basado en eventos. El sistema de suscripción a eventos UI 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

2
Hay un ejemplo bastante bueno del uso de puntero a miembro de datos en una función de plantilla en este código
alveko

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

79

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:

#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.


3
¿No debería ser &bowls.applesy &bowls.oranges? &bowl::applesy &bowl::orangesno apunta a nada.
Dan Nissenbaum

19
&bowl::applesy &bowl::orangesno 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.
John McFarlane

58

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:

// 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 vinculada, ¿no le gustaría algo como esto: void add (E * e) {e -> * next_ptr = head; cabeza = e; } ??
eeeeaaii

44
@eee te recomiendo que leas sobre los parámetros de referencia. Lo que hice es básicamente equivalente a lo que hiciste.
Johannes Schaub - litb

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

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

41

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:

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 medio 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 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:

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 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:

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, 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:

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


Esto es excelente. Estoy a punto de implementar algo muy similar, ¡y ahora no tengo que descubrir la extraña sintaxis! ¡Gracias!
Nicu Stiurca

Esta es la mejor respuesta. La double Sample::*parte es la clave!
Eyal

37

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

99
@thecoshman: depende por completo: ocultar miembros de datos detrás de los métodos set / get no es una encapsulación y simplemente un intento de las lecheras de abstracción de la interfaz. En muchos escenarios, la "desnormalización" para los miembros públicos es una opción razonable. Pero esa discusión probablemente excede los límites de la funcionalidad del comentario.
peterchen

44
+1 para 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

@Fellowshee Entiendes correctamente :) (enfatizó eso en la respuesta).
Peterter

26

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á 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.


¿Podría compartir un ejemplo de fragmento de código donde esta construcción sea útil? Gracias.
Ashwin Nanjappa

2
Actualmente estoy haciendo mucho de esto debido al trabajo de DCOM y al uso de clases de recursos administrados, lo que implica hacer un poco de trabajo antes de cada llamada, y el uso de miembros de datos para la representación interna para enviar a com, además de plantillas, hace mucho código de placa de caldera mucho más pequeño
Dan

19

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::firsty ::secondcuando 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;
}

11

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]).

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

Más a menudo he visto esto implementado usando una unión anónima que incluye un campo de matriz v [3] ya que eso evita una indirección, pero inteligente y potencialmente útil para campos no contiguos.
Dwayne Robinson

2
@DwayneRobinson, pero el uso de un unionjuego 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.
underscore_d

Es un buen ejemplo, pero el operador [] puede reescribirse sin puntero a componente: float *component[] = { &x, &y, &z }; return *component[idx];es decir, el puntero a componente parece no tener otro propósito que la ofuscación.
tobi_s

2

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


Básicamente, puede lograr lo mismo con el resumen Algorithmy dos clases derivadas, por ejemplo, AlgorithmAy AlgorithmB. En tal caso, ambos algoritmos están bien separados y se garantiza que se prueben de forma independiente.
shycha

2

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:

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 4th Edition


0

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.


0

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
}

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 foos 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, 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 ambos


0

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


El enlace ha estado muerto. Es por eso que aquí no se esperan respuestas de solo enlace. Al menos resuma el contenido del enlace, de lo contrario su respuesta se
volverá
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.