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



>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::list& phones);

>private:

>std::string theName;

>std::string theAddress;

>std::list thePhones;

>int numTimesConsulted;

>};

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

>const std::list& 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::list& phones)

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

>:theAddress(address),

>thePhones(phones),

>:numTimesConsulted(0)

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


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