Wskaźnik do członka danych klasy "::*"

Natknąłem się na dziwny fragment kodu, który dobrze kompiluje:

class Car
{
    public:
    int speed;
};

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

Dlaczego czy C++ ma ten wskaźnik do niestatycznego elementu danych klasy? Jakie jest użycie tego dziwnego wskaźnika w prawdziwym kodzie?

Author: L. F., 2009-03-22

15 answers

Jest to "wskaźnik do członka" - poniższy kod ilustruje jego użycie:

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

Jeśli chodzi o Dlaczego chcesz to zrobić, cóż, daje Ci to kolejny poziom indykacji, który może rozwiązać kilka trudnych problemów. Ale szczerze mówiąc, nigdy nie musiałem ich używać w moim własnym kodzie.

Edit: nie mogę myśleć o przekonującym użyciu wskaźników do danych członkowskich. Pointer to member functions can be used in pluggable architectures, but once again making an example in a mała przestrzeń mnie pokonuje. Poniżej znajduje się moja najlepsza (nieprzetestowana) próba - funkcja Apply, która wykonałaby pewne wstępne i końcowe przetwarzanie przed zastosowaniem wybranej przez użytkownika funkcji członka do obiektu:

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

Nawiasy wokół {[2] } są konieczne, ponieważ operator ->* ma niższy priorytet niż operator wywołania funkcji.

 200
Author: Oktalist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-31 20:57:28

Jest to najprostszy przykład, jaki mogę sobie wyobrazić, który przekazuje rzadkie przypadki, w których ta funkcja jest istotna:

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

Należy tu zwrócić uwagę na wskaźnik przekazany do count_fruit. Dzięki temu nie musisz pisać oddzielnych funkcji count_apples i count_oranges.

 84
Author: John McFarlane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-29 16:05:59

Innym zastosowaniem są listy inwazyjne. Typ elementu może wskazywać listę, jakie są jej następne / poprzednie wskaźniki. Tak więc lista nie używa mocno zakodowanych nazw, ale nadal może używać istniejących wskaźników:

// 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);
}
 62
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-23 00:25:36

Oto prawdziwy przykład, nad którym obecnie pracuję, z systemów przetwarzania sygnałów / sterowania:

Załóżmy, że masz jakąś strukturę, która reprezentuje dane, które zbierasz:

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

Teraz Załóżmy, że wpychasz je do wektora:

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

Teraz Załóżmy, że chcesz obliczyć jakąś funkcję (powiedzmy średnią) jednej ze zmiennych w zakresie próbek i chcesz włączyć tę średnią obliczenia do funkcji. Pointer-to-member sprawia, że łatwe:

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

Uwaga edytowana 2016/08/05 dla bardziej zwięzłego podejścia do funkcji szablonu

I, oczywiście, możesz obliczyć średnią dla dowolnego iteratora forward I dowolnego typu wartości, który obsługuje dodawanie ze sobą i dzielenie przez 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);

Edycja-powyższy kod ma wpływ na wydajność

Powinieneś zauważyć, jak szybko odkryłem, że powyższy kod ma poważne konsekwencje dla wydajności. Podsumowanie jest takie, że jeśli jesteś obliczając statystykę sumaryczną w szeregu czasowym lub obliczając FFT itp., należy przechowywać wartości dla każdej zmiennej w pamięci. W przeciwnym razie, iteracja nad seriami spowoduje brak pamięci podręcznej dla każdej pobranej wartości.

Rozważmy wykonanie tego kodu:

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;

Na wielu architekturach jedna instancja Sample wypełni linię pamięci podręcznej. Tak więc przy każdej iteracji pętli, jedna próbka zostanie pobrana z pamięci podręcznej. 4 bajty z linii cache zostanie użyty, a reszta wyrzucona, a następna iteracja spowoduje kolejny brak pamięci podręcznej, dostęp do pamięci i tak dalej.

O wiele lepiej to zrobić:
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;

Teraz, gdy pierwsza wartość x zostanie załadowana z pamięci, następne trzy zostaną również załadowane do pamięci podręcznej( zakładając odpowiednie wyrównanie), co oznacza, że nie potrzebujesz żadnych wartości załadowanych dla kolejnych trzech iteracji.

Powyższy algorytm można nieco poprawić poprzez zastosowanie instrukcji SIMD na np. SSE2 architektury. Jednak, te działają dużo lepiej, jeśli wszystkie wartości są sąsiadujące ze sobą w pamięci i można użyć jednej instrukcji do załadowania czterech próbek razem (więcej w późniejszych wersjach SSE).

YMMV-Zaprojektuj swoje struktury danych, aby pasowały do twojego algorytmu.

 44
Author: Tom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-05 13:49:42

Możesz później uzyskać dostęp do tego członka, na dowolnej instancji :

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

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

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

  return 0;
}

Zauważ, że potrzebujesz instancji, aby ją wywołać, więc nie działa ona jak delegat.
Jest używany rzadko, potrzebowałem go może raz lub dwa w ciągu wszystkich moich lat.

Zwykle użycie interfejsu (tj. czystej klasy bazowej w C++) jest lepszym wyborem.

 37
Author: peterchen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-22 16:58:03

IBM ma więcej dokumentacji, jak tego używać. Krótko mówiąc, używasz wskaźnika jako przesunięcia do klasy. Nie możesz używać tych wskaźników poza klasą, do której się odnoszą, więc:

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

Wydaje się to trochę niejasne, ale jedną z możliwych aplikacji jest to, że próbujesz napisać kod do deserializacji ogólnych danych na wiele różnych typów obiektów, a Twój kod musi obsługiwać typy obiektów, o których nie wie absolutnie nic (na przykład twój kod znajduje się w bibliotece, i obiekty, do których deserializujesz, zostały utworzone przez użytkownika twojej Biblioteki). Wskaźniki member dają ogólny, pół-czytelny sposób odwoływania się do poszczególnych przesunięć prętów danych, bez konieczności uciekania się do bezpisowych pustych * sztuczek, tak jak w przypadku struktur C.

 26
Author: AHelps,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-18 04:54:08

Umożliwia łączenie zmiennych i funkcji Członkowskich w jednolity sposób. Poniżej znajduje się przykład z klasą samochodu. Bardziej powszechnym zastosowaniem byłoby Wiązanie std::pair::first i ::second przy użyciu algorytmów STL i Boost na mapie.

#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;
}
 19
Author: Alex B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-22 13:08:36

Możesz użyć tablicy wskaźnika do (jednorodnych) danych składowych, aby włączyć interfejs z dwoma nazwami składowymi (np. x.data) i array-subscript (np. 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;
}
 11
Author: Functastic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-23 17:33:05

Jednym ze sposobów, którego użyłem, jest to, że mam dwie implementacje, jak zrobić coś w klasie i chcę wybrać jedną w czasie wykonywania, bez konieczności ciągłego przechodzenia przez instrukcję if, tj.

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

Oczywiście jest to praktycznie użyteczne tylko wtedy, gdy czujesz, że kod jest na tyle wbity, że instrukcja if spowalnia rzeczy zrobione np. głęboko w wnętrznościach jakiegoś intensywnego algorytmu. Nadal uważam, że jest bardziej elegancki niż stwierdzenie if nawet w sytuacjach, gdy nie ma praktyczne zastosowanie, ale to tylko mój opnion.

 2
Author: Troubadour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-23 18:40:08

Wskaźniki do klas nie są rzeczywistymi wskaźnikami ; klasa jest konstrukcją logiczną i nie ma fizycznego istnienia w pamięci, jednak gdy konstruujesz wskaźnik do członka klasy, daje przesunięcie do obiektu klasy członka, w którym można go znaleźć; daje to ważny wniosek: ponieważ statyczne elementy nie są powiązane z żadnym obiektem, więc wskaźnik do członka nie może wskazywać na statyczny element(dane lub funkcje) w ogóle Rozważ "po": {]}

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

Źródło: The Complete Reference C++ - Herbert Schildt 4th Edition

 2
Author: Arijit Dey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-04-10 15:11:00

Oto przykład, w którym wskaźnik do członków danych może być przydatny:

#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
Author: prestokeys,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-18 18:48:27

Załóżmy, że masz strukturę. Wewnątrz tej struktury znajdują się * jakieś imię * dwie zmienne tego samego typu, ale o innym znaczeniu

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

Dobra, powiedzmy, że masz kilkafoos w pojemniku:

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

Ok, Załóżmy, że załadujesz dane z oddzielnych źródeł, ale dane są prezentowane w ten sam sposób (np. potrzebujesz tej samej metody parsowania).

Mógłbyś zrobić coś takiego:

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

W tym momencie wywołanie readValues() zwróci kontener z unisonem "input-a" i "input-b"; wszystkie klucze będą obecne, a foos z Mają albo a, albo b albo oba.

 0
Author: inetknght,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-12 15:08:47

W rzeczywistości przykład wskaźnika do member może być bardziej wąskim konstruktorem aliasingu dla std:: shared_ptr:

template <typename T>
template <typename U>
shared_ptr<T>::shared_ptr(const shared_ptr<U>, T U::*member);

Do czego ten konstruktor byłby dobry

Załóżmy, że masz strukturę Foo:

struct foo {
    int ival;
    float fval;
};

Jeśli podałeś shared_ptr foo, możesz pobrać shared_ptr do jego członków ival lub fval używając tego konstruktora:

auto foo_shared = std::make_shared<foo>();
auto ival_shared = std::shared_ptr<int>(foo_shared, &foo::ival);

Byłoby to przydatne, jeśli chcemy przekazać wskaźnik foo_shared - >ival do jakiejś funkcji, która oczekuje shared_ptr

Https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr

 0
Author: benb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-20 17:09:30

Myślę, że chciałbyś to zrobić tylko wtedy, gdy dane członkowskie są dość duże (np. obiekt innej dość dużej klasy) i masz jakąś zewnętrzną rutynę, która działa tylko na referencjach do obiektów tej klasy. Nie chcesz kopiować obiektu członkowskiego, więc możesz go przekazać.

 -1
Author: Andrew Jaffe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-22 10:47:02

Aby dodać kilka przypadków użycia dla odpowiedzi @anon & @Oktalist, oto świetny materiał do czytania o funkcji wskaźnik-do-członka i wskaźnik-do-członka-danych.

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

 -1
Author: Dragonly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-12 19:15:54