6.2.3. Константные параметры и аргументы

При использовании параметров, являющихся константой, следует помнить об обсуждении спецификатора const верхнего уровня из раздела 2.4.3. Как упоминалось в этом разделе, спецификатор const верхнего уровня — это тот спецификатор, который относится непосредственно к объекту:

const int ci = 42;  // нельзя изменить ci; const верхнего уровня

int i = ci;         // ok: при копировании ci спецификатор const

                    // верхнего уровня игнорируется

int * const p = &i; // const верхнего уровня; нельзя присвоить p

*p = 0;             // ok: изменение при помощи p возможно; i теперь 0

Как и при любой другой инициализации, при копировании аргумента для инициализации параметра спецификаторы const верхнего уровня игнорируются. В результате спецификатор const верхнего уровня для параметров игнорируется. Параметру, у которого есть спецификатор const верхнего уровня, можно передать и константный, и неконстантный объект:

void fcn(const int i) { /* fcn может читать, но не писать в i */ }

Функцию fcn() можно вызвать, передав ей аргумент типа const int или обычного типа int. Тот факт, что спецификаторы const верхнего уровня игнорируются у параметра, может иметь удивительные последствия:

void fcn(const int i) { /* fcn может читать, но не писать в i */ }

void fcn(int i) { /* ... */ } // ошибка: переопределяет fcn(int)

В языке С++ можно определить несколько разных функций с одинаковым именем. Однако это возможно только при достаточно большом различии их списков параметров. Поскольку спецификаторы const верхнего уровня игнорируются, мы можем передать те же типы любой версии функции fcn(). Вторая версия функции fcn() является ошибкой. Несмотря на внешний вид, ее список параметров не отличается от списка первой версии функции fcn().

Параметры в виде указателей или ссылок и константность

Поскольку параметры инициализируются так же, как и переменные, имеет смысл напомнить общие правила инициализации. Можно инициализировать объект со спецификатором const нижнего уровня неконстантным объектом, но не наоборот, а простую ссылку следует инициализировать объектом того же типа.

int i = 42;

const int *cp = &i; // ok: но cp не может изменить i (раздел 2.4.2)

const int &r = i;   // ok: но r не может изменить i (раздел 2.4.1)

const int &r2 = 42; // ok: (раздел 2.4.1)

int *p = cp;  // ошибка: типы p и cp не совпадают (раздел 2.4.2)

int &r3 = r;  // ошибка: типы r3 и r не совпадают (раздел 2.4.1)

int &r4 = 42; // ошибка: нельзя инициализировать простую ссылку из

              //         литерала (раздел 2.3.1)

Те же правила инициализации относятся и к передаче параметров:

int i = 0;

const int ci = i;

string::size_type ctr = 0;

reset(&i);  // вызывает версию функции reset с параметром типа int*

reset(&ci); // ошибка: нельзя инициализировать int* из указателя на

            //         объект const int

reset(i);   // вызывает версию функции reset с параметром типа int&

reset(ci);  // ошибка: нельзя привязать простую ссылку к константному

            //         объекту ci

reset(42);  // ошибка: нельзя привязать простую ссылку к литералу

reset(ctr); // ошибка: типы не совпадают; ctr имеет беззнаковый тип

// ok: первый параметр find_char является ссылкой на константу

find_char("Hello World!", 'o', ctr);

Ссылочную версию функции reset() (см. раздел 6.2.2) можно вызвать только для объектов типа int. Нельзя передать литерал, выражение, результат которого будет иметь тип int, объект, который требует преобразования, или объект типа const int. Точно так же версии функции reset() с указателем можно передать только объект типа int* (см. раздел 6.2.1). С другой стороны, можно передать строковый литерал как первый аргумент функции find_char() (см. раздел 6.2.2). Ссылочный параметр этой функции — ссылка на константу, и можно инициализировать ссылки на константу из литералов.

По возможности используйте ссылки на константы

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

В качестве примера рассмотрим функцию find_char() из раздела 6.2.2. Строковый параметр этой функции правильно сделан ссылкой на константу. Если бы этот параметр был определен как string&:

// ошибка: первый параметр должен быть const string&

string::size_type find_char(string &s, char c,

                            string::size_type &occurs);

то вызвать ее можно было бы только для объекта класса string, так что

find_char("Hello World", 'o', ctr);

привело бы к неудаче во времени компиляции.

Более того, эту версию функции find_char() нельзя использовать из других функций, которые правильно определяют свои параметры как ссылки на константу. Например, мы могли бы использовать функцию find_char() в функции, которая определяет, является ли строка предложением:

bool is_sentence(const string &s) {

 // если в конце s есть точка, то строка s - предложение

 string::size_type ctr = 0;

 return find_char(s, ctr) == s.size() - 1 && ctr == 1;

}

Если бы функция find_char() получала простую ссылку string?, то этот ее вызов привел бы к ошибке при компиляции. Проблема в том, что s — ссылка на const string, но функция find_char() была неправильно определена как получающая простую ссылку.

Было бы заманчиво попытаться исправить эту проблему, изменив тип параметра в функции is_sentence(). Но это только распространит ошибку, так как вызывающая сторона функции is_sentence() сможет передавать только неконстантные строки.

Правильный способ решения этой проблемы — исправить параметр функции find_char(). Если невозможно изменить функцию find_char(), определите локальную копию строки s в функции is_sentence() и передавайте эту строку функции find_char().

Упражнения раздела 6.2.3

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

bool is_empty(string& s) { return s.empty(); }

Упражнение 6.17. Напишите функцию, определяющую, содержит ли строка какие-нибудь заглавные буквы. Напишите функцию, переводящую всю строку в нижний регистр. Использованные в этих функциях параметры имеют тот же тип? Если да, то почему? Если нет, то тоже почему?

Упражнение 6.18. Напишите объявления для каждой из следующих функций. Написав объявления, используйте имя функции для обозначения того, что она делает.

(a) Функция compare() возвращает значение типа bool и получает два параметра, являющиеся ссылками на класс matrix.

(b) Функция change_val() возвращает итератор vector<int> и получает два параметра: один типа int, а второй итератор для вектора vector<int>.

Упражнение 6.19. С учетом следующего объявления определите, какие вызовы допустимы, а какие нет. Объясните, почему они недопустимы.

double calc(double);

int count(const string &, char);

int sum(vector<int>::iterator, vector<int>::iterator, int);

vector<int> vec(10);

(a) calc(23.4, 55.1); (b) count("abcda", 'a');

(c) calc(66);         (d) sum(vec.begin(), vec.end(), 3.8);

Упражнение 6.20. Когда ссылочные параметры должны быть ссылками на константу? Что будет, если сделать параметр простой ссылкой, когда это могла быть ссылка на константу?

Более 800 000 книг и аудиокниг! 📚

Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением

ПОЛУЧИТЬ ПОДАРОК