Указатель на элемент данных класса «:: *»


243

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

class Car
{
    public:
    int speed;
};

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

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


Вот где я его нашел, и меня это смутило ... но теперь имеет смысл: stackoverflow.com/a/982941/211160
HostileFork говорит, что не доверяйте SE

Ответы:


190

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

#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необходимы, потому что ->*оператор имеет более низкий приоритет, чем оператор вызова функции.


3
Не могли бы вы показать пример сложной ситуации, когда это полезно? Спасибо.
Эшвин Нанджаппа

У меня есть пример использования указателя на член в классе черт в другом ответе SO .
Майк ДеСимон,

Примером является написание класса «обратного вызова» для некоторой системы, основанной на событиях. Например, система подписки на события пользовательского интерфейса CEGUI принимает шаблонный обратный вызов, в котором хранится указатель на выбранную вами функцию-член, чтобы вы могли указать метод для обработки события.
Бенджи XVI

2
Существует довольно прохладный пример стрелочных-to - данных -Члена использования в шаблонной функции в этом коде
alveko

3
Недавно я использовал указатели на элементы данных в рамках сериализации. Статический объект маршаллера был инициализирован списком оболочек, содержащих указатель на сериализуемые элементы данных. Ранний прототип этого кода.
Алексей Бирюков

79

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

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


3
Не должно ли быть &bowls.applesи &bowls.oranges? &bowl::applesи &bowl::orangesне указывает ни на что.
Дан Ниссенбаум

19
&bowl::applesи &bowl::orangesне указывать на членов объекта ; они указывают на членов класса . Они должны быть объединены с указателем на реальный объект, прежде чем они указывают на что-то. Эта комбинация достигается с ->*оператором.
Джон Макфарлейн

58

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

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

Если это действительно связанный список, не хотите ли вы что-то вроде этого: void add (E * e) {e -> * next_ptr = head; голова = е; } ??
eeeeaaii

4
@eee Я рекомендую вам прочитать о параметрах ссылки. То, что я сделал, в основном эквивалентно тому, что ты сделал.
Йоханнес Шауб -

+1 для вашего примера кода, но я не видел необходимости в использовании указателя на член, какой-либо другой пример?
Олкотт

3
@Alcott: Вы можете применить его к другим структурам, похожим на связанный список, где следующий указатель не назван next.
icktoofay

41

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

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

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 - проектируйте свои структуры данных в соответствии с вашим алгоритмом.


Это отлично. Я собираюсь реализовать нечто очень похожее, и теперь мне не нужно выяснять странный синтаксис! Спасибо!
Нику Стирка,

Это лучший ответ. double Sample::*Часть является ключом!
Eyal

37

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

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


Но наверняка это просто плохая практика? должен делать что-то вроде youcar.setspeed (mycar.getpspeed)
thecoshman

9
@thecoshman: полностью зависит - сокрытие элементов данных за методами set / get - это не инкапсуляция, а просто попытка доярки к абстракции интерфейса. Во многих случаях «денормализация» для публичных членов является разумным выбором. Но это обсуждение, вероятно, выходит за рамки функциональности комментариев.
peterchen

4
+1 за указание, если я правильно понимаю, что это указатель на член любого экземпляра, а не указатель на конкретное значение одного экземпляра, то есть части, которую я полностью пропустил.
Джонбейкерс

@Fellowshee Вы все правильно поняли :) (подчеркнул, что в ответе).
peterchen

26

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

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

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


Не могли бы вы поделиться примером кода, где эта конструкция полезна? Спасибо.
Эшвин Нанджаппа

2
В настоящее время я делаю многое из-за того, что выполняю некоторую работу DCOM и использую управляемые классы ресурсов, которые включают в себя выполнение работы перед каждым вызовом и использование элементов данных для внутреннего представления для отправки в com, плюс создание шаблонов, делает много Код котельной плиты намного меньше
Дан

19

Это позволяет связывать переменные-члены и функции единообразно. Ниже приведен пример с вашим классом автомобилей. Более распространенное использование будет обязательным 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;
}

11

Вы можете использовать массив указателей на (однородные) данные членов, чтобы включить двойной интерфейс, именованный член (iexdata) и массив подстрочный (т. Е. 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;
}

Я чаще видел, как это реализовано с использованием анонимного объединения, включающего поле массива v [3], поскольку это позволяет избежать косвенного обращения, но, тем не менее, является умным и потенциально полезным для несмежных полей.
Дуэйн Робинсон

2
@DwayneRobinson, но стандартное использование unionтипа to-pun не допускается стандартом, так как оно вызывает множество форм неопределенного поведения ... тогда как этот ответ в порядке.
underscore_d

Это хороший пример, но оператор [] может быть переписан без указателя на компонент: float *component[] = { &x, &y, &z }; return *component[idx];Т.е. указатель на компонент, кажется, не имеет смысла, кроме запутывания.
tobi_s

2

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


По сути, вы можете достичь того же с помощью абстрактных Algorithmи двух производных классов, например, AlgorithmAи AlgorithmB. В таком случае оба алгоритма хорошо разделены и должны быть проверены независимо.
Шича

2

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

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-е издание


0

Я думаю, что вы захотите сделать это, только если данные члена были довольно большими (например, объект другого довольно здоровенного класса), и у вас есть некоторая внешняя подпрограмма, которая работает только со ссылками на объекты этого класса. Вы не хотите копировать объект-член, так что это позволяет вам передавать его.


0

Вот пример, где может быть полезен указатель на члены данных:

#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

Предположим, у вас есть структура. Внутри этой структуры есть * какое-то имя * две переменные одного типа, но с разным значением

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

Хорошо, теперь допустим, у вас есть куча foos в контейнере:

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

Хорошо, теперь предположим, что вы загружаете данные из отдельных источников, но данные представлены таким же образом (например, вам нужен тот же метод синтаксического анализа).

Вы могли бы сделать что-то вроде этого:

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

В этот момент вызов readValues()возвратит контейнер с унисонами «input-a» и «input-b»; все ключи будут присутствовать, и у foos есть либо a, либо b, либо оба.


0

Просто добавьте несколько вариантов использования для ответа @ anon's & @ Oktalist, вот отличный материал для чтения о указателе на функцию-член и указатель на член-данные.

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


ссылка была мертва Вот почему ответы только для ссылок здесь не ожидаются. По крайней мере, суммируйте содержание ссылки, в противном случае ваш ответ станет недействительным, когда ссылка
перестанет работать
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.