Указатель на элемент данных класса ":: *" - PullRequest
212 голосов
/ 22 марта 2009

Я наткнулся на этот странный фрагмент кода, который прекрасно компилируется:

class Car
{
    public:
    int speed;
};

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

Почему имеет C ++ этот указатель на нестатический член данных класса? Что использует этот странный указатель в реальном коде?

Ответы [ 14 ]

169 голосов
/ 22 марта 2009

Это «указатель на член» - следующий код иллюстрирует его использование:

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

Что касается , почему вы бы хотели это сделать, это дает вам еще один уровень косвенности, который может решить некоторые сложные проблемы. Но, честно говоря, мне никогда не приходилось использовать их в своем собственном коде.

Редактировать: Не могу придумать, как убедительно использовать указатели на данные членов. Указатель на функции-члены можно использовать в подключаемых архитектурах, но повторение примера в небольшом пространстве меня побеждает. Следующее - моя лучшая (не проверенная) попытка - функция Apply, которая выполняла бы некоторую предварительную и последующую обработку перед применением выбранной пользователем функции-члена к объекту:

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

Скобки вокруг c->*func необходимы, поскольку оператор ->* имеет более низкий приоритет, чем оператор вызова функции.

70 голосов
/ 29 апреля 2011

Это самый простой пример, который я могу себе представить, который передает редкие случаи, когда эта особенность уместна:

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

Здесь следует отметить указатель, передаваемый count_fruit. Это избавляет вас от необходимости писать отдельные функции count_apples и count_oranges.

56 голосов
/ 23 марта 2009

Другим приложением являются навязчивые списки. Тип элемента может сказать списку, каковы его следующие / предыдующие указатели. Таким образом, список не использует жестко закодированные имена, но все еще может использовать существующие указатели:

// 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);
}
35 голосов
/ 22 марта 2009

Позже вы сможете получить доступ к этому члену на любом экземпляре:

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

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

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

  return 0;
}

Обратите внимание, что вам нужен экземпляр для его вызова, поэтому он не работает как делегат.
Он используется редко, мне это нужно было, может быть, один или два раза за все мои годы.

Обычно лучше использовать интерфейс (т. Е. Чистый базовый класс в C ++).

34 голосов
/ 02 ноября 2010

Вот реальный пример, над которым я сейчас работаю, из систем обработки / управления сигналами:

Предположим, у вас есть структура, которая представляет данные, которые вы собираете:

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

Теперь предположим, что вы складываете их в вектор:

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

Теперь предположим, что вы хотите вычислить некоторую функцию (скажем, среднее) одной из переменных в диапазоне выборок, и вы хотите разделить это среднее значение в функцию. Указатель на член упрощает:

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

Примечание Отредактировано 2016/08/05 для более краткого подхода к шаблонам

И, конечно, вы можете создать шаблон для вычисления среднего для любого прямого итератора и любого типа значения, который поддерживает сложение с самим собой и деление на 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);

РЕДАКТИРОВАТЬ - приведенный выше код влияет на производительность

Вы должны заметить, как я вскоре обнаружил, что приведенный выше код имеет некоторые серьезные последствия для производительности. Суть в том, что если вы вычисляете сводную статистику по временному ряду или вычисляете БПФ и т. Д., То вы должны хранить значения для каждой переменной непрерывно в памяти. В противном случае итерация по серии вызовет пропадание кэша для каждого полученного значения.

Учитывайте производительность этого кода:

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;

На многих архитектурах один экземпляр Sample заполнит строку кэша. Таким образом, на каждой итерации цикла один образец будет извлекаться из памяти в кэш. 4 байта из строки кэша будут использованы, а остальные будут выброшены, и следующая итерация приведет к еще одному отсутствию кэша, доступу к памяти и т. Д.

Намного лучше сделать это:

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;

Теперь, когда первое значение x загружено из памяти, следующие три также будут загружены в кэш (при условии подходящего выравнивания), что означает, что вам не нужно загружать какие-либо значения для следующих трех итераций.

Приведенный выше алгоритм может быть несколько улучшен за счет использования инструкций SIMD, например, для архитектур SSE2. Тем не менее, они работают намного лучше, если все значения непрерывны в памяти, и вы можете использовать одну инструкцию для загрузки четырех выборок вместе (больше в более поздних версиях SSE).

YMMV - разработайте структуры данных в соответствии с вашим алгоритмом.

26 голосов
/ 22 марта 2009

IBM имеет еще немного документации о том, как это использовать. Вкратце, вы используете указатель в качестве смещения в классе. Вы не можете использовать эти указатели отдельно от класса, к которому они относятся, поэтому:

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

Это кажется немного неясным, но одно из возможных приложений - это если вы пытаетесь написать код для десериализации родовых данных во множество различных типов объектов, и ваш код должен обрабатывать типы объектов, о которых он абсолютно ничего не знает (например, ваш код находится в библиотеке, а объекты, в которые вы десериализуетесь, были созданы пользователем вашей библиотеки). Указатели на элементы дают вам общий, полуразборчивый способ ссылки на отдельные смещения элементов данных, не прибегая к бесчисленным трюкам void *, как вы могли бы использовать для структур Си.

18 голосов
/ 22 марта 2009

Позволяет связывать переменные-члены и функции единообразно. Ниже приведен пример с вашим классом автомобилей. Более распространенное использование будет привязка std::pair::first и ::second при использовании в алгоритмах STL и Boost на карте.

#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;
}
8 голосов
/ 23 марта 2009

Вы можете использовать массив указателей на (однородные) данные элемента, чтобы включить двойной интерфейс, именованный элемент (т.е. x.data) и массив подстрочный (т.е. 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;
}
2 голосов
/ 22 марта 2009

Один из способов, которым я воспользовался, - это если у меня есть две реализации того, как что-то сделать в классе, и я хочу выбрать одну во время выполнения без необходимости постоянно проходить оператор 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;
};

Очевидно, что это практически полезно, только если вы чувствуете, что код достаточно забит, что оператор if замедляет выполнение, например. в глубине души какой-то интенсивный алгоритм где-то. Я все еще думаю, что это более элегантно, чем выражение if, даже в ситуациях, когда оно не имеет практического применения, но это только мое мнение.

1 голос
/ 27 июня 2018

Указатели на классы не являются реальными указателями; класс является логической конструкцией и не имеет физического существования в памяти, однако, когда вы создаете указатель на член класса, он дает смещение в объект класса члена, где член может быть найден; Это дает важный вывод: Поскольку статические члены не связаны ни с каким объектом, указатель на член НЕ МОЖЕТ указывать на статический член (данные или функции) вообще Учтите следующее:

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

Источник: Полный справочник C ++ - Герберт Шильдт, 4-е издание

...