Неявные и явные конструкторы

Временные объекты могут создаваться в довольно неожиданных контекстах. Если их создание/уничтожение связано с выделением каких-либо ресурсов, а количество создаваемых экземпляров достаточно велико, следует аккуратно следить за их возможным избыточным созданием.

Такое странное на первый взгляд поведение объясняется наличием у класса Stack конструктора, принимающего единственный аргумент типа int

Stack ( int size );

При разрешении перегрузки - конструктора копий и оператора присвоения, компилятор видит данный конструктор и успешно воспринимает его как возможность для неявного преобразования типа

Существуют случаи, когда такое поведение является желательным и облегчающим труд программиста. Например, при использовании класса std::string из стандартной библиотеки можно неявно сконструировать подходящий объект из обычного строкового литерала.

Однако, для случая класса Stack такое поведение скорее обескураживает. Такое неявное преобразование типа с созданием временного объекта можно запретить, если пометить используемый для вызова конструктор как ЯВНЫЙ при помощи ключевого слова explicit:

explicit Stack ( int _size );

Имеет смысл сразу определять как явные все конструкторы, принимающие один аргумент, во избежание неожиданных преобразований типа. Разрешать неявное создание объектов нужно только в тех случаях, где это действие имеет практический смысл, как в случае класса-строки.

Однако сам модификатор explicit не обязательно должен ограничиваться конструкторами с одним аргументом. Использование этого модификатора также может быть полезным для конструкторов, имеющих значения аргументов по умолчанию.

Когда часть аргументов имеют значения по умолчанию, вполне вероятен сценарий неявного конструирования объектов, если конструктор не имеет модификатора explicit:

Полезен ли для объектов класса неявный синтаксис создания или вреден - решать разработчику класса исходя из естественного смысла реализуемых понятий.

Запрещение копирования

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

Во избежание случайного вызова, копирование и присвоение можно запретить, поместив объявления конструктора копий и оператора присвоений в зону доступа private. При этом, реализовывать данные функции-члены не имеет смысла. Попытка вызова из функций вне класса приведет к ошибке из-за прав доступа. Попытка вызова из функций внутри класса приведет к ошибке на этапе компоновки, поскольку функции, хотя и доступны, фактически не реализованы.

Оптимизация копирования

Большинство современных компиляторов С++ реализуют необязательную по стандарту языка оптимизацию RVO (Return Value Optimization), чтобы уменьшить число копируемых объектов.

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

Некоторые компиляторы также поддерживают дополнительную более сложную оптимизацию NRVO (Named Return Value Optimization), когда из функции возвращается не временный объект, а обычный локальный объект. Для многих случаев компилятор в состоянии вычислить что именно данный объект будет возвращен вызывающей стороне, и аналогично, вместо создания собственного объекта использует неявно передаваемый адрес возврата.

Основные отличия между классами-значениями и классами-сущностями. Запрещение копирования объектов.

Наши рекомендации