it-swarm.com.de

Zeiger auf Klassendatenelement ":: *"

Ich bin auf dieses seltsame Code-Snippet gestoßen, das sich gut kompilieren lässt:

class Car
{
    public:
    int speed;
};

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

Warum hat C++ diesen Zeiger auf ein nicht statisches Datenelement einer Klasse? Was ist die Verwendung dieses seltsamen Zeigers in echtem Code?

200
Ashwin Nanjappa

Es ist ein "Zeiger auf Mitglied" - der folgende Code veranschaulicht seine Verwendung:

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

In Bezug auf warum möchten Sie das tun, gut, es gibt Ihnen eine andere Ebene der Umleitung, die einige knifflige Probleme lösen kann. Aber um ehrlich zu sein, ich musste sie nie in meinem eigenen Code verwenden.

Edit: Ich kann nicht an eine überzeugende Verwendung von Zeigern auf Mitgliederdaten denken. Zeiger auf Member-Funktionen können in steckbaren Architekturen verwendet werden, aber noch einmal ein Beispiel auf kleinem Raum zu produzieren, besiegt mich. Das Folgende ist mein bester (ungetesteter) Versuch - eine Apply-Funktion, die vor dem Anwenden einer vom Benutzer ausgewählten Elementfunktion auf ein Objekt eine Vor-und Nachbearbeitung durchführt:

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

Die Klammern um c->*func sind erforderlich, weil der ->*-Operator eine niedrigere Priorität hat als der Funktionsaufrufoperator.

160
anon

Dies ist das einfachste Beispiel, von dem ich mir vorstellen kann, dass es die seltenen Fälle vermittelt, in denen diese Funktion relevant ist: 

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

Zu beachten ist hier der Zeiger, der an count_fruit übergeben wird. Dadurch müssen Sie keine separaten Funktionen für count_apples und count_oranges schreiben.

66
John McFarlane

Eine andere Anwendung sind aufdringliche Listen. Der Elementtyp kann der Liste sagen, was die nächsten/vorherigen Zeiger sind. Die Liste verwendet also keine hartcodierten Namen, kann jedoch vorhandene Zeiger verwenden:

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

Sie können später auf dieses Member auf any instance zugreifen:

int main()
{    
  int Car::*pSpeed = &Car::speed;    
  Car myCar;
  Car yourCar;

  int mySpeed = myCar.*pSpeed;
  int yourSpeed = yourCar.*pSpeed;

  assert(mySpeed > yourSpeed); // ;-)

  return 0;
}

Beachten Sie, dass Sie zum Aufrufen eine Instanz benötigen, damit sie nicht wie ein Delegat funktioniert.
Es wird selten verwendet, ich habe es in meinen Jahren vielleicht ein- oder zweimal gebraucht.

Normalerweise ist die Verwendung einer Schnittstelle (d. H. Einer reinen Basisklasse in C++) die bessere Wahl beim Entwurf.

33
peterchen

Hier ist ein reales Beispiel, an dem ich gerade arbeite, von Signalverarbeitungs-/Steuersystemen:

Angenommen, Sie haben eine Struktur, die die erfassten Daten darstellt:

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

Angenommen, Sie füllen sie in einen Vektor:

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

Angenommen, Sie möchten eine Funktion (sagen wir den Mittelwert) einer der Variablen über einen Bereich von Stichproben berechnen, und Sie möchten diese Mittelwertberechnung in eine Funktion einrechnen. Der Zeiger auf Mitglied macht es einfach:

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

Hinweis Herausgegeben 2016/08/05 für einen prägnanteren Ansatz mit Template-Funktionen

Natürlich können Sie es als Vorlage verwenden, um einen Mittelwert für jeden Forward-Iterator und jeden Werttyp zu berechnen, der die Addition mit sich selbst und die Division nach Größe_t unterstützt:

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 - Der obige Code hat Auswirkungen auf die Leistung

Wie ich bald entdeckte, sollten Sie beachten, dass der obige Code einige schwerwiegende Auswirkungen auf die Leistung hat. Die Zusammenfassung ist, dass, wenn Sie eine Zusammenfassungsstatistik für eine Zeitreihe oder eine FFT usw. berechnen, die Werte für jede Variable zusammenhängend im Speicher gespeichert werden sollten. Andernfalls führt das Durchlaufen der Reihe zu einem Cache-Fehler für jeden abgerufenen Wert.

Betrachten Sie die Leistung dieses Codes:

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;

In vielen Architekturen füllt eine Instanz von Sample eine Cachezeile. Bei jeder Wiederholung der Schleife wird also ein Sample aus dem Speicher in den Cache gezogen. 4 Bytes aus der Cache-Zeile werden verwendet und der Rest wird verworfen, und die nächste Iteration führt zu einem weiteren Cache-Miss, Speicherzugriff usw.

Viel besser das zu tun:

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;

Wenn nun der erste x-Wert aus dem Speicher geladen wird, werden die nächsten drei ebenfalls in den Cache geladen (vorausgesetzt, es wird eine geeignete Ausrichtung verwendet), sodass für die nächsten drei Iterationen keine Werte geladen werden müssen.

Der obige Algorithmus kann durch die Verwendung von SIMD-Befehlen auf SSE2-Architekturen noch etwas verbessert werden. Diese arbeiten jedoch viel besser, wenn die Werte alle im Speicher zusammenhängend sind und Sie eine einzelne Anweisung verwenden können, um vier Samples zusammen zu laden (mehr in späteren Versionen SSE).

YMMV - Gestalten Sie Ihre Datenstrukturen entsprechend Ihrem Algorithmus.

30
Tom

IBM enthält weitere Informationen zur Verwendung dieses Befehls. Kurz, Sie verwenden den Zeiger als Versatz in die Klasse. Sie können diese Zeiger nicht neben der Klasse verwenden, auf die sie sich beziehen.

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

Es scheint ein wenig obskur, aber eine mögliche Anwendung ist, wenn Sie versuchen, Code zu schreiben, um generische Daten in viele verschiedene Objekttypen zu deserialisieren, und Ihr Code muss Objekttypen behandeln, über die er absolut nichts weiß (zum Beispiel ist Ihr Code dies.) in einer Bibliothek und die Objekte, in die Sie deserialisieren, wurden von einem Benutzer Ihrer Bibliothek erstellt). Die Member-Zeiger geben Ihnen einen generischen, halb lesbaren Weg, um auf die einzelnen Daten-Member-Offsets zu verweisen, ohne auf typlose void * Tricks zurückgreifen zu müssen, wie Sie dies für C-Strukturen tun.

24
AHelps

Es ermöglicht die einheitliche Verknüpfung von Elementvariablen und Funktionen. Das Folgende ist ein Beispiel für Ihre Autoklasse. Eine gebräuchlichere Verwendung wäre std::pair::first und ::second bei Verwendung in STL-Algorithmen und Boost auf einer Karte.

#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;
}
18
Alex B

Sie können ein Array von Zeiger auf (homogene) Elementdaten verwenden, um eine duale Schnittstelle mit benannten Mitgliedern (d. H. X.data) und Array-Subscript (d. H. X [idx]) zu aktivieren.

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

Eine Art, wie ich es benutzt habe, ist, wenn ich zwei Implementierungen habe, wie man etwas in einer Klasse macht, und ich möchte eine zur Laufzeit auswählen, ohne ständig eine if-Anweisung durchlaufen zu müssen, d. H.

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

Offensichtlich ist dies nur praktisch, wenn Sie der Meinung sind, dass der Code so stark gehämmert wird, dass die if -Anweisung Dinge verlangsamt, z. tief in den Innern eines intensiven Algorithmus irgendwo. Ich denke immer noch, dass es eleganter ist als die if-Aussage, auch wenn es keinen praktischen Nutzen hat, aber das ist nur meine Meinung.

2
Troubadour

Zeiger auf Klassen sind keine realen Zeiger; Eine Klasse ist ein logisches Konstrukt und hat keine physische Existenz im Speicher. Wenn Sie jedoch einen Zeiger auf ein Member einer Klasse erstellen, wird ein Offset in ein Objekt der Klasse des Members angegeben, in dem das Member gefunden werden kann. Dies ergibt eine wichtige Schlussfolgerung: Da statische Member keinem Objekt zugeordnet sind, kann ein Zeiger auf ein Member NICHT auf ein statisches Member (Daten oder Funktionen) zeigen, unabhängig davon, ob

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

Quelle: Die vollständige Referenz C++ - Herbert Schildt 4. Ausgabe

0
Arijit Dey

Hier ein Beispiel, in dem ein Zeiger auf Datenelemente nützlich sein kann:

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

Ich denke, dass Sie dies nur tun möchten, wenn die Mitgliederdaten ziemlich groß sind (z. B. ein Objekt einer anderen ziemlich heftigen Klasse) und Sie eine externe Routine haben, die nur auf Verweise auf Objekte dieser Klasse wirkt. Sie möchten das Member-Objekt nicht kopieren, damit Sie es weitergeben können.

0
Andrew Jaffe

Um nur einige Anwendungsfälle für die Antwort von @ anon & @ Oktalist hinzuzufügen, hier ist ein großartiges Lesematerial über die Zeiger-auf-Member-Funktion und Zeiger-auf-Member-Daten. http://www.cs.wustl.edu/~schmidt/PDF/C++-ptmf4.pdf

0
Dragonly

Angenommen, Sie haben eine Struktur. Innerhalb dieser Struktur befinden sich * Eine Art Name * Zwei Variablen desselben Typs, jedoch mit unterschiedlicher Bedeutung

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

Okay, nehmen wir an, Sie haben eine Menge foos in einem Container:

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

Angenommen, Sie laden die Daten aus unterschiedlichen Quellen, aber die Daten werden auf dieselbe Weise dargestellt (z. B. benötigen Sie dieselbe Parsing-Methode).

Sie könnten so etwas tun:

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

An diesem Punkt wird mit dem Aufruf von readValues() ein Container mit dem Unison "input-a" und "input-b" zurückgegeben. Alle Tasten sind vorhanden, und für foos gibt es entweder a oder b oder beide.

0
inetknght