Указатели на поля и методы класса

Последнее обновление: 01.08.2025

В C++ существуют не только обычные указатели, которые хранят адрес объекта в памяти. Есть и более специфический инструмент — указатели на члены класса. Это особый вид указателей, который "указывает" не на конкретный объект, а на само поле или метод внутри определения класса.

В отличие от обычного указателя, который хранит абсолютный адрес в памяти, указатель на член класса хранит, по сути, смещение (offset) поля данных или адрес функции-члена относительно начала любого объекта этого класса. Сами по себе они бесполезны; их сила раскрывается только при применении к конкретному экземпляру класса. Посмотрим, как они работают.

Указатели на члены класса не часто используются, но в некоторых сценариях они незаменимы. Некоторые ситуации применения:

  • Реализация паттерна "Стратегия" или "Команда": объект может хранить указатель на метод, который определяет его текущее поведение (стратегию)

  • Обработка данных по шаблону: можно создать массив указателей на поля и итерироваться по ним для выполнения однотипных операций (сериализация, валидация, вычисление суммы).

  • Диспетчеризация вызовов: в событийных системах, управляемых событиями, можно составить карту.словарь (например, std::map), где ключом является некое событие, а значением — указатель на метод-обработчик этого события.

Указатели на поля класса/структуры

Указатели на поля класса/структуры представляют простейший вид указателей на члены класса. Они позволяют выбрать, с каким полем объекта работать, уже во время выполнения программы. Их формальный синтаксис вовлекает три компонента:

  1. Объявление

    тип_поля Класс::*имя_указателя;
  2. Инициализация

    имя_указателя = &Класс::имя_поля;
  3. Использование

    объект.*имя_указателя (оператор "точка-звездочка")
    указатель_на_объект->*имя_указателя (оператор "стрелка-звездочка")
    

Рассмотрим простейший пример:

#include <iostream>
#include <string>

struct Person{
    std::string name;
    int age;
};

int main() {
    // Объявляем указатель на член класса Person типа int
    int Person::*p_age;

    // Инициализируем указатель - указываем на поле age
    p_age = &Person::age; 

    // Используем указатель
    Person tom{"Tom", 41};  // объект для примера
    std::cout << "Person age: " << tom.*p_age << std::endl;  // Person age: 41


    // Для примера используем указатель через указатель на объект
    Person bob{"Bob", 46};
    Person* p_bob = &bob;
    std::cout << "Person age: " << p_bob->*p_age << std::endl;  // Person age: 46

    return 0;
}

Рассмотрим по этапно. Сначала объявляем указатель на член класса Person типа int, который называется "p_age":

int Person::*p_age;

Далее инициализируем указатель - он указывает на поле age:

p_age = &Person::age; 

Далее используем указатель для обращения к полям объекта. Для примера я использовал доступ как через объект, так и через указатель на объект:

Person tom{"Tom", 41};  // объект для примера
  std::cout << "Person age: " << tom.*p_age << std::endl;  // Person age: 41

Использование указателей в данном примере, конечно, избыточно. Поэтому рассмотрим еще несколько примеров.

Допустим, у нас будет класс Product с двумя числовыми полями: цена и скидка:

#include <iostream>
#include <string>

struct Product {
    std::string name;
    double price;
    double min_discount;
    double max_discount;

    void print_final_price(double Product::*discount) {
        // Применяем указатель на член к this
        double final_price = this->price - (this->*discount);
        std::cout << "Final price for " << this->name << " is: " << final_price << std::endl;
    }
};

int main() {
    Product phone{"Smartphone", 1000.0, 50.0, 200.0};

    // В зависимости от условия, наш указатель будет "смотреть" на разные поля
    std::cout << "Min discount:" << std::endl;
    phone.print_final_price(&Product::min_discount); // 1000 - 50 = 950

    std::cout << "Max discount:" << std::endl;
    phone.print_final_price(&Product::max_discount); // 1000 - 200 = 800


    phone.max_discount = 250; // Меняем скидку для демонстрации
    phone.print_final_price(&Product::max_discount); // 1000 - 250 = 750

    return 0;
}

Здесь цель — динамически выбрать, какое из числовых полей Product использовать для вычисления скидки (max_discount или min_discount).

Метод print_final_price класса Product принимает указатель на член:

void print_final_price(double Product::*discount) {
    
    double final_price = this->price - (this->*discount);
    std::cout << "Final price for " << this->name << " is: " << final_price << std::endl;
}

В качестве параметра в функцию передается указатель double Product::*discount. Он не хранит значение, а лишь "знает", как получить доступ к полю типа double внутри любого объекта Product.

Внутри метода print_final_price используется оператор .* в выражении this->*discount. Это позволяет применить переданный указатель к текущему объекту (this).

В функции main при вызове phone.print_final_price(&Product::min_amount) мы передаем "адрес" поля min_discount:

phone.print_final_price(&Product::min_discount);

В результате метод вычисляет price - min_discount. Если бы мы передали указатель на другое совместимое поле, расчет был бы иным.

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

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

phone.max_discount = 250; // Меняем скидку для демонстрации
phone.print_final_price(&Product::max_discount); // 1000 - 250 = 750

Аналогично мы можем динамически присваивать значение указателю:

#include <iostream>
#include <string>

struct Product {
    std::string name;
    double price;
    double min_discount;
    double max_discount;

};

int main() {
    // Объявляем указатель на член класса Product типа double
    double Product::*discount;

    Product phone{"Smartphone", 1000.0, 50.0, 200.0};

    // В зависимости от условия, наш указатель будет "смотреть" на разные поля
    if (phone.price > 500) {
        // Указываем на поле min_discount
        discount = &Product::min_discount; 
    } else {
        // Указываем на поле max_discount
        discount = &Product::max_discount;
    }
    
    // используем указатель
    std::cout << "Final discount:" << phone.*discount << std::endl;  // Final discount:50

    phone.min_discount = 80.0;  // меняем значение
    std::cout << "Final discount:" << phone.*discount << std::endl;  // Final discount:80
    return 0;
}

И рассмотрим еще один пример:

#include <iostream>
#include <string>

struct Person {
    std::string name;
    int age;
    int salary;             // зарплата
    int rental_income;      // доход от недвижимости
    int other_income;       // какой-то другой доход
};

int main() {

    Person tom{"Tom", 41, 4500, 1500, 400};

    // массив указателей
    int Person::*required_fields[] = {
        &Person::salary,
        &Person::rental_income,
        &Person::other_income,
    };

    int total_income = 0;
    for (auto income : required_fields) {
        total_income += tom.*income;
    }

    std::cout << "Total income: " << total_income <<  std::endl;  // Total income: 6400
    return 0;
}

Здесь определена структура Person, которая представляет человека и которая определяет salary (зарплата), rental_income (доход от недвижимости), other_income (какой-то другой доход).

В функции main создаeтся массив указателей на члены класса Person с именем required_fields. Этот массив инициализируется указателями на три поля: salary, rental_income и other_income. По сути, мы создали список полей, которые хотим обработать.

int Person::*required_fields[] = {
        &Person::salary,
        &Person::rental_income,
        &Person::other_income,
    };

Далее в цикле вычисляем суммарный доход человека, используя поля в этом массиве:

int total_income = 0;
for (auto income : required_fields) {
    total_income += tom.*income;
}

Цикл for перебирает каждый элемент в массиве required_fields. В каждой итерации income становится одним из указателей на член (&Person::salary, затем &Person::rental_income и т.д.). И значение соответствующего поля добавляется к переменной total_income.

Указатели на функции-члены

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

  • Объявление:

    возвращаемый_тип (Класс::*имя_указателя)(параметры);
  • Инициализация:

    имя_указателя = &Класс::имя_метода;
  • Использование:

    (объект.*имя_указателя)(аргументы);
    (указатель_на_объект->*имя_указателя)(аргументы);
    

Причем скобки вокруг (объект.*имя_указателя) обязательны из-за низкого приоритета операторов .* и ->*.

Рассмотрим простейший пример:

Добавим в наш класс `Product` разные способы расчета скидки.
#include <iostream>
#include <string>

struct Product {
    std::string name;
    double price;

    double get_fixed_discount(double amount) {
        return amount;
    }

    double get_percentage_discount(double percent) {
        return price * (percent / 100.0);
    }
};

int main() {
    // Объявляем указатель на метод класса Product, который принимает double и возвращает double
    double (Product::*p_discount_calculator)(double);

    Product tv{"Big TV", 2000.0};

    bool holiday_season = true;
    if (holiday_season) {
        // Указываем на метод, считающий скидку в процентах
        p_discount_calculator = &Product::get_percentage_discount;
        double discount = (tv.*p_discount_calculator)(15.0); // Вызываем get_percentage_discount(15.0)
        std::cout << "Holiday discount: " << discount << std::endl; // 300
        std::cout << "Final price: " << tv.price - discount << std::endl; // 1700
    } else {
        // Указываем на метод с фиксированной скидкой
        p_discount_calculator = &Product::get_fixed_discount;
        double discount = (tv.*p_discount_calculator)(100.0); // Вызываем get_fixed_discount(100.0)
        std::cout << "Regular discount: " << discount << std::endl; // 100
        std::cout << "Final price: " << tv.price - discount << std::endl; // 1900
    }

    return 0;
}

В данном случае в класс Product добавлены разные способы расчета скидки:

double get_fixed_discount(double amount) {
    return amount;
}

double get_percentage_discount(double percent) {
    return price * (percent / 100.0);
}

В функции main объявляем указатель на метод класса Product, который принимает double и возвращает double

double (Product::*p_discount_calculator)(double);

Далее с помощью условной конструкции динамически инициализиурем этот указатель и применяем его для вычисления скидки:

if (holiday_season) {
    p_discount_calculator = &Product::get_percentage_discount;
    double discount = (tv.*p_discount_calculator)(15.0); // Вызываем get_percentage_discount(15.0)
    ..............
} else {
    p_discount_calculator = &Product::get_fixed_discount;
    double discount = (tv.*p_discount_calculator)(100.0); // Вызываем get_fixed_discount(100.0)
    ......................
}
Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850