Что ж, это корректное и желательное поведение. В конец концов, вы хотите, чтобы все ваши объекты были надежно инициализированы и уничтожены. И все же было бы неплохо найти способ пропустить все эти вызовы конструкторов и деструкторов. Способ есть! Это – передача по ссылке на константу:
bool validateStudent(const Student& s);
Этот способ гораздо эффективнее: не вызываются никакие конструкторы и деструкторы, поскольку не создаются никакие новые объекты. Квалификатор const в измененном объявлении параметра важен. Исходная версия validateStudent принимала параметр Student по значению, вызвавший ее знает о том, что он защищен от любых изменений, которые функция может внести в переданный ей объект; validateStudent сможет модифицировать только его копию. Теперь же, когда Student передается по ссылке, необходимо объявить его const, поскольку в противном случае вызывающая программа должна побеспокоиться о том, чтобы validateStudent не вносила изменений в переданный ей объект.
Передача параметров по ссылке также позволяет избежать проблемы « срезки » (slicing). Когда объект производного класса передается (по значению) как объект базового класса, вызывается конструктор копирования базового класса, а те части, которые принадлежат производному, «срезаются». У вас остается только простой объект базового класса – что вполне естественно, так как его создал конструктор базового класса. Это почти всегда не то, что вам нужно. Например, предположим, что вы работаете с набором классов для реализации графической оконной системы:
class Window {
public
...
std::string name() const; // возвращает имя окна
virtual void display() const; // рисует окно и его содержимое
};
class WindwoWithScrollBars: public Window {
public:
...
virtual void display() const;
};
Все объекты класса Window имеют имя, которое вы можете получить посредством функции name, и все окна могут быть отображены, на что указывает наличие функции display. Тот факт, что display – функция виртуальная, говорит о том, что способ отображения простых объектов базового класса Window может отличаться от способа отображения объектов WindowWithScrollBar (см. правила 34 и 36).
Теперь предположим, что вы хотите написать функцию, которая будет печатать имя окна и затем отображать его. Вот неверный способ написания такой функции:
void printNameAndDisplay(Window w) // неправильно! Параметр
{ // может быть «срезан»
std::cout << w.name();
w.display();
}
Посмотрим, что случится, если вызвать эту функцию, передав ей объект WindowWithScrollBar:
WindowWithScrollBar wwsb;
PrintNameAndDisplay(wwsb);
Параметр w будет сконструирован – он передан по значению, помните? – как объект Window, и вся дополнительная информация, которая делает его объектом WindowWithScrollBar, будет срезана. Внутри printNameAndDisplay w всегда будет вести себя как объект класса Window (потому что это и есть объект класса Window), независимо от типа объекта, в действительности переданного функции. В частности, вызов функции display внутри printNameAndDisplay всегда вызовет Window::display и никогда – WindowWithScrollBar::display.
Способ решения проблемы «срезки» – передать w по ссылке на константу:
void printNameAndDisplay(const Window& w) // правильно, параметр
{ // не может быть «срезан»
std::cout << w.name();
w.display();
}
Теперь w ведет себя правильно, какое бы окно он ни представлял в действительности.
Если вы заглянете «под капот» C++, то увидите, что ссылки обычно реализуются как указатели, поэтому передача чего-либо по ссылке обычно означает передачу указателя. В результате объекты встроенного типа (например, int) всегда более эффективно передавать по значению, чем по ссылке. Поэтому для встроенных типов, если у вас есть выбор – передавать по значению или по ссылке на константу, имеет смысл выбрать передачу по значению. Тот же совет касается итераторов и функциональных объектов STL, потому что они специально спроектированы для передачи по значению. Программисты, реализующие итераторы и функциональные объекты, отвечают за то, чтобы обеспечить эффективность передачи их по значению и исключить «срезку». Это пример того, как меняются правила в зависимости от используемой вами части C++ (см. правило 1).
Встроенные типы являются небольшими объектами, поэтому некоторые делают вывод, что все встроенные типы – хорошие кандидаты на передачу по значению, даже если они определены пользователем. Сомнительно. То, что объект небольшой, еще не значит, что вызов его конструктора копирования обойдется дешево. Многие объекты – среди них большинство контейнеров STL – содержат в себе немногим больше обычного указателя, но копирование таких объектов влечет за собой копирование всего, на что они указывают. Это может оказаться очень дорого.
Читать дальше
Конец ознакомительного отрывка
Купить книгу