Правило 26: Откладывайте определение переменных насколько возможно
Всякий раз при объявлении переменной, принадлежащий типу, в котором есть конструктор или деструктор, программа тратит время на ее конструирование, когда поток управления достигнет определения переменной, и на уничтожение – при выходе переменной из области видимости. Эти накладные расходы приходится нести даже тогда, когда переменная не используется, и, разумеется, их хотелось бы избежать.
Вероятно, вы думаете, что никогда не объявляете неиспользуемых переменных, но так ли это? Рассмотрим следующую функцию, которая возвращает зашифрованный пароль при условии, что его длина не меньше некоторого минимума. Если пароль слишком короткий, функция возбуждает исключение типа logic_error, определенное в стандартной библиотеке C++ (см. правило 54):
// эта функция объявляет переменную encrypted слишком рано
std::string encryptPassword(const std::string password)
{
using namespace std;
string encrypted;
if(password.length MinimumPasswordLength) {
throw logic_error(“Слишком короткий пароль”);
}
... // сделать все, что необходимо для помещения
// зашифрованного пароля в переменную encrypted
return encrypted;
}
Нельзя сказать, что объект encrypted в этой функции совсем уж не используется, но он не используется в случае, когда возбуждается исключение. Другими словами, вы платите за вызов конструктора и деструктора объекта encrypted, даже если функция encryptPassword возбуждает исключение. Так не лучше ли отложить определение переменной encrypted до того момента, когда вы будете
// в этой функции определение переменной encrypted отложено до момента,
// когда в ней возникает надобность
std::string encryptPassword(const std::string password)
{
using namespace std;
if(password.length MinimumPasswordLength) {
throw logic_error(“Слишком короткий пароль”);
}
string encrypted;
... // сделать все, что необходимо для помещения
// зашифрованного пароля в переменную encrypted
return encrypted;
}
Этот код все еще не настолько компактный, как мог бы быть, потому что переменная encrypted определена без начального значения. А значит, будет использован ее конструктор по умолчанию. Часто первое, что нужно сделать с объектом, – это дать ему какое-то значение, нередко посредством присваивания. В правиле 4 объяснено, почему конструирование объектов по умолчанию с последующим присваиванием значения менее эффективно, чем инициализация нужным значением с самого начала. Это относится и к данному случаю. Например, предположим, что для выполнения «трудной» части работы функция encryptPassword вызывает следующую функцию:
void encrypt(std::string s); // шифрует s по месту
Тогда encryptPassword может быть реализована следующим образом, хотя и это еще не оптимальный способ:
// в этой функции определение переменной encrypted отложено до момента,
// когда в ней возникает надобность, но и этот вариант еще недостаточно
// эффективен
std::string encryptPassword(const std::string password)
{
... // проверка длины
string encrypted; // конструктор по умолчанию
encrypted = password; // присваивание encrypted
encrypt(encrypted);
return encrypted;
}
Еще лучше инициализировать encrypted параметром password, избежав таким образом потенциально дорогостоящего конструктора по умолчанию:
// а это оптимальный способ определения и инициализации encrypted
std::string encryptPassword(const std::string password)
{
... // проверка длины
string encrypted(password); // определение и инициализация
// конструктором копирования
encrypt(encrypted);
return encrypted;
}