Читаем Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ полностью

Другая причина того, почему маленькие пользовательские типы не обязательно хороши для передачи по значению, заключается в том, что их размер подвержен изменениям. Тип, который мал сегодня, может вырасти в будущем, потому что его внутренняя реализация может измениться. Ситуация меняется даже в том случае, если вы переключаетесь на другую реализацию C++. Например, в одних реализациях тип string из стандартной библиотеки в семь раз больше, чем в других.

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

Что следует помнить

• Передаче по значению предпочитайте передачу по ссылке на константу. Обычно это более эффективно и позволяет избежать проблемы «срезки».

• Это правило не касается встроенных типов, итераторов и функциональных объектов STL. Для них передача по значению обычно подходит больше.

Правило 21: Не пытайтесь вернуть ссылку, когда должны вернуть объект

Как только программисты осознают проблемы эффективности, связанные с передачей объектов по значению (см. правило 20), они, подобно крестоносцам, преисполняются решимости искоренить зло – передачу по значению – везде, где бы оно ни пряталось. Непреклонные в своем «святом» порыве, они с неизбежностью допускают фатальную ошибку: начинают передавать по ссылке значения несуществующих объектов. А это неправильно.

Рассмотрим класс для представления рациональных чисел, включающий в себя дружественную функцию для перемножения двух таких чисел:


class Rational {

public:

Rational(int numerator = 0, // см. в правиле 24 – почему этот

int denominator = 1); // конструктор не explicit

...

private:

int n, d;

friend

const Rational // см. в правиле 3 -

operator*(const Rational lhs, // почему возвращаемый тип const

const Rational rhs);

};


Ясно, что эта версия operator* возвращает результирующий объект по значению, и вы обнаружили бы непрофессиональный подход, если бы не уделили внимания вопросу о затратах на создание и удаление объекта. Вы не хотите платить за то, за что платить не должны. Отсюда вопрос: должны ли вы платить?

Нет, если можете вернуть ссылку. Но ссылка – это просто другое имя некоторого существующего объекта. Всякий раз, сталкиваясь с объявлением ссылки, вы должны спросить себя: для чего предназначено это имя, ведь оно должно принадлежать чему-то. В случае operator*, если функция возвращает ссылку, значит, она должна вернуть ссылку на некоторый уже существующий объект Rational, который и содержит произведение двух объектов, которые следовало перемножить.

Очевидно, нет никаких оснований полагать, что такой объект существует до вызова operator*. Например, если у вас есть


Rational a(1, 2); // a = 1/2

Rational a(3, 5); // b = 3/5

Rational c = a*b; // c должно равняться 3/10


то неразумно ожидать, что уже существует то рациональное число со значением три десятых. Если operator* будет возвращать такое число, то он должен создать его самостоятельно.

Функция может создать новый объект только двумя способами: в стеке или в куче. Создание в стеке осуществляется посредством определения локальной переменной. Используя эту стратегию, вы можете попытаться написать operator* так:


const Rational operator*(const Rational lhs, // предупреждение!

const Rational rhs) // плохой код!

{

Rational result(lhs.n * rhs.h, lhs.d * rhs.d);

return result;

}


Этот подход можно отвергнуть сразу, потому что вашей целью было избежать вызова конструктора, а result должен быть создан, подобно любому другому объекту. Кроме того, эта функция порождает и более серьезную проблему, поскольку возвращает ссылку на result, но result – это локальный объект, а локальные объекты разрушаются при завершении функции, в которой они объявлены. Таким образом, эта версия operator* возвращает ссылку не на Rational, а на бывший Rational – пустой, отвратительный, гнилой скелет того, что когда-то было объектом Rational, но уже не является таковым, потому что он уничтожен. Стоит вызвать эту функцию – вы попадете в область неопределенного поведения. Запомним: любая функция, которая возвращает ссылку на локальный объект, некорректна (то же касается и функций, возвращающих указатель на локальный объект).

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже