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

Сформулируем правила, которые описывают, когда инициализация объекта гарантируется, а когда нет. К сожалению, эти правила достаточно сложны – на мой взгляд, слишком сложны, чтобы их стоило запоминать. Вообще, если вы работаете с C-частью C++ (см. правило 1) и инициализация может стоить определенных затрат во время исполнения, то не гарантируется, что она произойдет. Это объясняет, почему содержимое массивов (в C-части C++) не обязательно инициализируется, а содержимое вектора (из STL-части C++) инициализируется всегда.

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


int x = 0; // ручная инициализация int

const char * text = “Строка в стиле C”; // ручная инициализация указателя

// (см. также правило 3)

double d; // «инициализация» чтением

std::cin d; // из входного потока


Почти во всех остальных случаях ответственность за инициализацию ложится на конструкторы. Правило простое: убедитесь, что все конструкторы инициализируют в объекте всё.

Этому правилу легко следовать, но важно не путать присваивание с инициализацией. Рассмотрим конструктор класса, представляющего записи в адресной книге:


class PhoneNumber {…}

class ABEntry { // ABEntry = “Address Book Entry”

public:

ABEntry(const std::string name, const std::string address,

const std::listPhoneNumber phones);

private:

std::string theName;

std::string theAddress;

std::listPhoneNumber thePhones;

int numTimesConsulted;

};

ABEntry(const std::string name, const std::string address,

const std::listPhoneNumber phones)

{

theName = name; // все это присваивание, а не инициализация

theAddress = address;

thePhones = phones;

numTimesConsulted = 0;

}


Да, в результате порождаются объекты ABEntry со значениями, которых вы ожидаете, но это все же не лучший подход. Правила C++ оговаривают, что члены объекта инициируются перед входом в тело конструктора. То есть внутри конструктора ABEntry члены theName, theAddress и thePhones не инициализируются, а им присваиваются значения. Инициализация происходит ранее: когда автоматически вызываются их конструкторы перед входом в тело конструктора ABEntry. Это не касается numTimesConsulted, поскольку этот член относится к встроенному типу. Для него нет никаких гарантий того, что он вообще будет инициализирован перед присваиванием.

Лучший способ написания конструктора ABEntry – использовать список инициализации членов вместо присваивания:


ABEntry(const std::string name, const std::string address,

const std::listPhoneNumber phones)

:theName(name), // теперь это все – инициализации

:theAddress(address),

thePhones(phones),

:numTimesConsulted(0)

{} // тело конструктора теперь пусто


Этот конструктор дает тот же самый конечный результат, что и предыдущий, но часто оказывается более эффективным. Версия, основанная на присваиваниях, сначала вызывает конструкторы по умолчанию для инициализации theName, theAddress и thePhones, а затем сразу присваивает им новые значения, затирая те, что уже были присвоены в конструкторах по умолчанию. Таким образом, вся работа конструкторов по умолчанию тратится впустую. Подход со списком инициализации членов позволяет избежать этой проблемы, поскольку аргументы в списке инициализации используются в качестве аргументов конструкторов для различных членов-данных. В этом случае theName создается конструктором копирования из name, theAddress – из address, thePhones – из phones. Для большинства типов единственный вызов конструктора копирования более эффективен – иногда намного более эффективен, чем вызов конструкторов по умолчанию с последующим вызовом операторов присваивания.

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