C++. Сборник рецептов.

15.2. Применение указателей для членов класса.

Проблема.

Требуется обеспечить адресную ссылку на данное-член или на функцию-член.

Решение.

Используйте имя класса и оператор области видимости ( ::) со звездочкой для правильного квалифицирования имени. Пример 15.2 показывает, как это можно сделать.

Пример 15.2. Получение указателя на член класса.

#include <iostream>

#include <string>

Class MyClass {

Public:

 MyClass() : ival_(0), sval_("foo") {}.

 ~MyClass() {}.

 void incr() {++ival_;}.

 void decr() {ival_--;}.

Private:

 std::string sval_;

 int ival_;

};

Int main() {

 MyClass obj;

 int MyClass::* mpi = &MyClass::ival_;         // Указатели на.

 std::string MyClass::* mps = &MyClass::sval_; // данные-члены.

 void (MyClass::*mpf)(); // Указатель на функцию-член, у которой.

                         // нет параметров и которая возвращает void.

 void (*pf)(); // Обычный указатель на функцию.

 int* pi = &obj.ival_; // int-указатель, ссылающийся на переменную-член.

                       // типа int, - все нормально.

 mpf = &MyClass::incr; // Указатель на функцию-член. Вы не можете.

                       // записать это значение в поток. Посмотрите в.

                       // отладчике, как это значение выглядит.

 pf = &MyClass::incr; // Ошибка: &MyClass::inc не является экземпляром.

                      // функции.

 std::cout << "mpi = " << mpi << '\n';

 std::cout << "mps = " << mps << '\n';

 std::cout << "pi = " << pi << '\n';

 std::cout << "*pi = " << *pi << '\n';

 obj.*mpi = 5;

 obj.*mps = "bar";

 (obj.*mpf)(); // теперь obj.ival_ равно 6.

 std::cout << "obj.ival_ = " << obj.ival_ << '\n';

 std::cout << "obj.sval_ = " << obj.sval_ << '\n';

}

Обсуждение.

Указатели на члены класса выглядят и работают иначе, чем обычные указатели. Прежде всего, они имеют «смешной» синтаксис (не вызывающий смех, но странный). Рассмотрим следующую строку из примера 15.2.

Int MyClass::* mpi = &MyClass::ival_;

Здесь объявляется указатель и ему присваивается значение целого типа, которым оказывается член класса MyClass. Две вещи отнимают это объявление от обычного int*. Во-первых, вам приходится вставлять имя класса и оператор области видимости между типом данного и звездочкой. Во-вторых, при выполнении операции присваивания этому указателю на самом деле не назначается какой то определенный адрес памяти. Значение &MyClass::ival_ не является каким-то конкретным значением, содержащимся в памяти; оно ссылается на имя класса, а не на имя объекта, но тогда что же это такое на самом деле? Можно представить это значение как смешение данного-члена относительно начального адреса объекта.

Переменная mpi должна использоваться совместно с экземпляром класса, к которому она применяется. Немного ниже в примере 15.2 располагается следующая строка, которая использует mpi для присваивания целого числа значению, на которое ссылается указатель mpi.

Obj.*mpi = 5;

Obj является экземпляром класса MyClass. Ссылка на член с использованием точки (или ->, если у вас имеется указатель на obj) и разыменование mpi позволяют вам получить ссылку на obj.ival_.

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

Void (MyClass::*mpf)();

Ему можно присвоить значение с помощью оператора адресации.

Mpf = &MyClass::incr;

Для вызова функции заключите основное выражение в скобки, чтобы компилятор понял ваши намерения, например:

(obj.*mpf)();

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

Int* pi = &obj.ival_;

Конечно, вы используете имя объекта, а не имя класса, потому что получаете адрес конкретного данного-члена конкретного объекта, расположенного где-то в памяти. (Однако обычно стараются адреса данных-членов класса не выдавать за его пределы, чтобы нельзя было их изменить из-за опрометчивых действий в клиентском программном коде.).

В отличие от данного члена с функцией-членом вы не можете сделать то же самое, потому что это бессмысленно. Рассмотрим указатель на функцию, имеющую такую же сигнатуру, как MyClass::incr (т.е. он возвращает void и не имеет аргументов).

Void (*pf)();

Теперь попытайтесь присвоить этому указателю адрес функции-члена.

Pf = &MyClass::incr; // He получится.

Pf = &obj.incr;      // И это не пройдет.

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

См. также.

Рецепт 15.1.