Выбрать главу

}

Обсуждение

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

15.3. Обеспечение невозможности модификации аргумента в функции

Проблема

Вы пишете функцию и требуется гарантировать, что ее аргументы не будут модифицированы при ее вызове.

Решение

Для предотвращения изменения аргументов вашей функцией объявите ее аргументы с ключевым словом const. Короткий пример 15.3 показывает, как это можно сделать.

Пример 15.3. Гарантия невозможности модификации аргументов

#include <iostream

#include <string>

void concat(const std::string& s1, // Аргументы объявлены как константное,

 const std::string& s2,            // поэтому не могут быть изменены

 std::string& out) {

 out = s1 + s2;

}

int main() {

 std::string s1 = "Cabo ";

 std::string s2 = "Wabo";

 std::string s3;

 concat(s1, s2, s3);

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

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

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

}

Обсуждение

В примере 15.3 продемонстрировано прямое использование ключевого слова const. Существует две причины объявления параметров вашей функции с этим ключевым словом, когда вы не планируете их изменять. Во-первых, этим вы сообщаете о своих намерениях читателям вашего программного кода. Объявляя параметр как const, вы фактически говорите, что он является входным параметром. Это позволяет пользователям вашей функции писать программный код в расчете на то, что эти значения не будут изменены. Во-вторых, это позволяет компилятору запретить любые модифицирующие операции на тот случай, если вы случайно их используете. Рассмотрим небезопасную версию concat из примера 15 3.

void concatUnsafe(std::string& s1,

 std::string& s2 std::string& out) {

 out = s1 += s2; // Ну вот, записано значение в s1

}

Несмотря на мою привычку тщательно подходить к кодированию программ, я сделал глупую ошибку и написал += вместо +. В результате при вызове concatUnsafe будут модифицированы аргументы out и s1, что может оказаться сюрпризом для пользователя, который едва ли рассчитывает на модификацию одной из исходных строк.

Спасти может const. Создайте новую функцию concatSafe, объявите переменные константными, как показано в примере 15.3, и функция не будет откомпилирована.

void concatSafe(const std::string& s1,

 const std::string& s2, std::string& out) {

 out = s1 += s2; // Теперь вы получите ошибку компиляции

}

concatSafе гарантирует неизменяемость значений в s1 и s2. Эта функция делает еще кое-что: она позволяет пользователю передавать константные аргументы. Например, программный код, выполняющий конкатенацию строк, мог бы выглядеть следующим образом.

void myFunc(const std::string& s) { // Обратите внимание, что s является

                                    // константной переменной

 std::string dest;

 std::string tmp = "foo";

 concatUnsafe(s, tmp, dest); // Ошибка: s - константная переменная

                             // Выполнить какие-то действия с dest...

}

В данном случае функция myFunc не будет откомпилирована, потому что concatUnsafe не обеспечивает const'антность myFunc. myFunc гарантирует внешнему миру, что она не будет модифицировать содержимое s, т.е. все действия с s внутри тела myFunc не должны нарушать это обещание. Конечно, вы можете обойти это ограничение, используя оператор const_cast и тем самым освобождаясь от константности, но такой подход ненадежен, и его следует избегать. В этой ситуации concatSafe будет компилироваться и выполняться нормально.