Puntatore al membro di dati della classe ":: *"


243

Mi sono imbattuto in questo strano frammento di codice che compila bene:

class Car
{
    public:
    int speed;
};

int main()
{
    int Car::*pSpeed = &Car::speed;
    return 0;
}

Perché C ++ ha questo puntatore a un membro di dati non statico di una classe? A che serve questo strano puntatore nel codice reale?


Ecco dove l'ho trovato, mi ha confuso troppo ... ma ha un senso ora: stackoverflow.com/a/982941/211160
HostileFork dice fiducia Dont SE

Risposte:


190

È un "puntatore al membro": il seguente codice ne illustra l'utilizzo:

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

Per quanto riguarda il motivo per cui vorresti farlo, ti dà un altro livello di riferimento indiretto che può risolvere alcuni problemi difficili. Ma ad essere sincero, non ho mai dovuto usarli nel mio codice.

Modifica: non riesco a pensare fuori mano a un uso convincente per i puntatori ai dati dei membri. Il puntatore alle funzioni membro può essere usato in architetture collegabili, ma ancora una volta produrre un esempio in un piccolo spazio mi sconfigge. Quello che segue è il mio miglior tentativo (non testato): una funzione Apply che eseguirà alcune elaborazioni pre e post prima di applicare una funzione membro selezionata dall'utente a un oggetto:

void Apply( SomeClass * c, void (SomeClass::*func)() ) {
    // do hefty pre-call processing
    (c->*func)();  // call user specified function
    // do hefty post-call processing
}

Le parentesi intorno c->*funcsono necessarie perché l' ->*operatore ha una precedenza inferiore rispetto all'operatore di chiamata della funzione.


3
Potresti mostrare un esempio di una situazione difficile in cui questo è utile? Grazie.
Ashwin Nanjappa,

Ho un esempio dell'uso del puntatore al membro in una classe Traits in un'altra risposta SO .
Mike DeSimone,

Un esempio sta scrivendo una classe di tipo "callback" per alcuni sistemi basati su eventi. Il sistema di abbonamento agli eventi dell'interfaccia utente di CEGUI, ad esempio, accetta un callback basato su modelli che memorizza un puntatore a una funzione membro di tua scelta, in modo da poter specificare un metodo per gestire l'evento.
Benji XVI,

2
C'è un bell'esempio di utilizzo da puntatore a dati -membro in una funzione template in questo codice
alveko

3
Di recente ho usato i puntatori ai membri dei dati nel framework di serializzazione. L'oggetto marshaller statico è stato inizializzato con un elenco di wrapper che contengono puntatore a membri di dati serializzabili. Un primo prototipo di questo codice.
Alexey Biryukov,

79

Questo è l'esempio più semplice che mi viene in mente che trasmette i rari casi in cui questa funzionalità è 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;
}

La cosa da notare qui è il puntatore passato a count_fruit. Questo ti evita di dover scrivere funzioni separate di count_apples e count_oranges.


3
Non dovrebbe essere &bowls.applese &bowls.oranges? &bowl::applese &bowl::orangesnon punta a nulla.
Dan Nissenbaum,

19
&bowl::applese &bowl::orangesnon indicano membri di un oggetto ; indicano i membri di una classe . Devono essere combinati con un puntatore a un oggetto reale prima che puntino a qualcosa. Tale combinazione si ottiene con l' ->*operatore.
John McFarlane,

58

Un'altra applicazione sono elenchi invadenti. Il tipo di elemento può dire all'elenco quali sono i suoi puntatori next / prev. Quindi l'elenco non utilizza nomi codificati ma può comunque utilizzare puntatori esistenti:

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

Se questa è veramente una lista collegata non vorresti qualcosa del genere: void add (E * e) {e -> * next_ptr = head; head = e; } ??
eeeeaaii,

4
@eee ti consiglio di leggere i parametri di riferimento. Quello che ho fatto è sostanzialmente equivalente a quello che hai fatto.
Johannes Schaub - litb

+1 per il tuo esempio di codice, ma non ho visto alcuna necessità per l'uso del puntatore al membro, qualche altro esempio?
Alcott,

3
@Alcott: è possibile applicarlo ad altre strutture simili a elenchi collegati in cui il puntatore successivo non è denominato next.
icktoofay,

41

Ecco un esempio reale su cui sto lavorando in questo momento, dai sistemi di elaborazione / controllo del segnale:

Supponiamo di avere una struttura che rappresenti i dati che stai raccogliendo:

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

Supponiamo ora di inserirli in un vettore:

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

Supponiamo ora di voler calcolare alcune funzioni (ad esempio la media) di una delle variabili su un intervallo di campioni e di voler fattorizzare questo calcolo medio in una funzione. Il puntatore al membro semplifica:

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 Modificato il 2016/08/05 per un approccio più conciso alla funzione modello

E, naturalmente, puoi modellarlo per calcolare una media per qualsiasi forward-iterator e qualsiasi tipo di valore che supporti l'aggiunta con se stesso e la divisione per 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);

EDIT - Il codice sopra ha implicazioni sulle prestazioni

Dovresti notare, come ho presto scoperto, che il codice sopra ha alcune gravi implicazioni sulle prestazioni. Il riepilogo è che se stai calcolando una statistica riassuntiva su una serie temporale, o calcolando una FFT ecc., Allora dovresti memorizzare i valori per ogni variabile contigui nella memoria. Altrimenti, l'iterazione sulla serie provocherà un errore nella cache per ogni valore recuperato.

Considera le prestazioni di questo codice:

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;

Su molte architetture, un'istanza di Sampleriempirà una riga della cache. Quindi ad ogni iterazione del ciclo, un campione verrà estratto dalla memoria nella cache. Verranno utilizzati 4 byte dalla riga della cache e il resto verrà eliminato e la successiva iterazione comporterà un'altra mancata cache, accesso alla memoria e così via.

Molto meglio fare questo:

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;

Ora, quando il primo valore x viene caricato dalla memoria, anche i successivi tre verranno caricati nella cache (supponendo un adeguato allineamento), il che significa che non è necessario caricare alcun valore per le successive tre iterazioni.

L'algoritmo di cui sopra può essere ulteriormente migliorato mediante l'uso di istruzioni SIMD su ad esempio architetture SSE2. Tuttavia, funzionano molto meglio se i valori sono tutti contigui in memoria e puoi usare una singola istruzione per caricare quattro campioni insieme (più nelle versioni SSE successive).

YMMV: progetta le tue strutture dati in base all'algoritmo.


Questo è eccellente Sto per implementare qualcosa di molto simile, e ora non devo capire la strana sintassi! Grazie!
Nicu Stiurca,

Questa è la risposta migliore La double Sample::*parte è la chiave!
Eyal,

37

Successivamente puoi accedere a questo membro, in qualsiasi 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;
}

Nota che hai bisogno di un'istanza per chiamarla, quindi non funziona come un delegato.
È usato raramente, ne ho avuto bisogno forse una o due volte in tutti i miei anni.

Normalmente usare un'interfaccia (cioè una pura classe base in C ++) è la scelta migliore per il design.


Ma sicuramente questa è solo una cattiva pratica? dovrebbe fare qualcosa di simile a youcar.setspeed (mycar.getpspeed)
thecoshman,

9
@thecoshman: dipende interamente - nascondere i membri dei dati dietro i metodi set / get non è incapsulamento e semplicemente un tentativo delle mungitrici di astrarre l'interfaccia. In molti scenari, la "denormalizzazione" per i membri pubblici è una scelta ragionevole. Ma quella discussione probabilmente supera i confini della funzionalità di commento.
peterchen,

4
+1 per indicare, se ho capito bene, che questo è un puntatore a un membro di qualsiasi istanza e non un puntatore a un valore specifico di un'istanza, che è la parte che mi mancava completamente.
johnbakers,

@Fellowshee Hai capito bene :) (sottolineato nella risposta).
Peter

26

IBM ha un po 'più di documentazione su come usarlo. In breve, stai usando il puntatore come offset nella classe. Non puoi usare questi puntatori a parte la classe a cui fanno riferimento, quindi:

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

Sembra un po 'oscuro, ma una possibile applicazione è se stai cercando di scrivere codice per deserializzare i dati generici in molti tipi di oggetti diversi e il tuo codice deve gestire tipi di oggetti di cui non sa assolutamente nulla (ad esempio, il tuo codice è in una libreria e gli oggetti in cui deserializzi sono stati creati da un utente della tua libreria). I puntatori del membro offrono un modo generico e semi-leggibile di riferirsi alle compensazioni dei singoli membri dei dati, senza dover ricorrere a un vuoto * non tipizzato, come si potrebbe fare per le strutture C.


Potresti condividere un esempio di frammento di codice in cui questo costrutto è utile? Grazie.
Ashwin Nanjappa,

2
Attualmente sto facendo molto a causa del lavoro di DCOM e dell'utilizzo di classi di risorse gestite che comportano un po 'di lavoro prima di ogni chiamata, e l'utilizzo di membri di dati per la rappresentazione interna da inviare a com, oltre a modelli, rende molto codice piastra caldaia molto più piccolo
Dan

19

Permette di associare variabili e funzioni dei membri in modo uniforme. Di seguito è riportato un esempio con la tua classe Car. Un utilizzo più comune sarebbe vincolante std::pair::firste ::secondquando si utilizza in algoritmi STL e Boost su una mappa.

#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

È possibile utilizzare un array di puntatori a dati (omogenei) dei membri per abilitare un'interfaccia dual, named-member (iexdata) e array-subscript (cioè 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;
}

L'ho visto più spesso implementato usando un'unione anonima che include un campo array v [3] poiché evita una indiretta, ma intelligente e potenzialmente utile per campi non contigui.
Dwayne Robinson,

2
@DwayneRobinson ma usare un uniontipo di gioco di parole in quel modo non è consentito dallo standard in quanto invoca numerose forme di comportamento indefinito ... mentre questa risposta è ok.
underscore_d

Questo è un esempio chiaro ma l'operatore [] può essere riscritto senza puntatore a componente: float *component[] = { &x, &y, &z }; return *component[idx];vale a dire, il puntatore a componente sembra non avere alcuno scopo se non l'offuscamento.
tobi_s

2

Un modo in cui l'ho usato è se ho due implementazioni su come fare qualcosa in una classe e voglio sceglierne una in fase di esecuzione senza dover passare continuamente attraverso un'istruzione if

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

Ovviamente questo è praticamente utile solo se ritieni che il codice sia stato martellato abbastanza da far sì che l'istruzione if rallenti le cose fatte ad es. nel profondo di un algoritmo intensivo da qualche parte. Penso ancora che sia più elegante dell'affermazione if anche in situazioni in cui non ha alcun uso pratico ma è solo la mia opinione.


In sostanza, è possibile ottenere lo stesso con l'astratto Algorithme due classi derivate, ad esempio, AlgorithmAe AlgorithmB. In tal caso, entrambi gli algoritmi sono ben separati e devono essere testati in modo indipendente.
shycha,

2

I puntatori alle classi non lo sono veri e propri puntatori; una classe è un costrutto logico e non ha esistenza fisica in memoria, tuttavia, quando si costruisce un puntatore a un membro di una classe, esso fornisce un offset in un oggetto della classe del membro in cui è possibile trovare il membro; Ciò fornisce una conclusione importante: poiché i membri statici non sono associati a nessun oggetto, quindi un puntatore a un membro NON PU point puntare a un membro statico (dati o funzioni) qualunque. Considerare quanto segue:

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

Fonte: The Complete Reference C ++ - Herbert Schildt 4th Edition


0

Penso che vorresti farlo solo se i dati dei membri erano piuttosto grandi (ad esempio, un oggetto di un'altra classe piuttosto pesante) e hai una routine esterna che funziona solo su riferimenti a oggetti di quella classe. Non vuoi copiare l'oggetto membro, quindi questo ti consente di passarlo in giro.


0

Ecco un esempio in cui il puntatore ai membri dei dati potrebbe essere utile:

#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

Supponiamo di avere una struttura. All'interno di quella struttura sono presenti * una sorta di nome * due variabili dello stesso tipo ma con significato diverso

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

Bene, ora diciamo che hai un sacco di foos in un contenitore:

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

Bene, ora supponiamo di caricare i dati da fonti separate, ma i dati sono presentati nello stesso modo (ad esempio, è necessario lo stesso metodo di analisi).

Potresti fare qualcosa del genere:

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

A questo punto, la chiamata readValues()restituirà un contenitore con un unisono di "input-a" e "input-b"; tutti i tasti saranno presenti e i foos con avranno a o b o entrambi.


0

Solo per aggiungere alcuni casi d'uso per la risposta di @ anon & @ Oktalist, ecco un ottimo materiale di lettura su dati puntatore-membro e puntatore-membro-membro.

https://www.dre.vanderbilt.edu/~schmidt/PDF/C++-ptmf4.pdf


il link è morto. Ecco perché qui non sono attese risposte solo link. Riassumi almeno il contenuto del collegamento, altrimenti la tua risposta non sarà valida quando il collegamento marcisce
phuclv,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.