должна использоваться совместно с экземпляром класса, к которому она применяется. Немного ниже в примере 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
будет компилироваться и выполняться нормально.
Указатели вносят темные штрихи в розовую картину
const
. Когда вы объявляете переменную-указатель как параметр, вы имеет дело с двумя объектами: самим адресом и то, на что ссылается этот адрес. C++ позволяет использовать
const
для ограничения действий по отношению к обоим объектам. Рассмотрим еще одну функцию конкатенации, которая использует указатели.