Предлагаемое улучшение скорости при определении строки со значением сразу, вместо задержки
В настоящее время я читаю "Язык программирования C++: специальное издание" Бьярна Страуструпа, и на странице 133 говорится следующее:
Для пользовательских типов откладывание определения переменной до появления подходящего инициализатора также может привести к повышению производительности. Например:
string s; /* .... */ s = "The best is the enemy of the good.";
может быть намного медленнее, чем
string s = "Voltaire";
Я знаю, что это может легко произойти, что означает, что это не обязательно так, однако давайте просто скажем, что это происходит.
Почему это может увеличить производительность?
Это только так с пользовательскими типами (или даже типами STL), или это также имеет место с int
, float
, так далее?
7 ответов
Я бы сказал, что это в основном типы с нетривиальными конструкторами по умолчанию, по крайней мере, что касается производительности.
Разница между двумя подходами заключается в том, что:
- В первой версии сначала создается пустая строка (с использованием конструктора по умолчанию); затем оператор присваивания используется для эффективного отбрасывания работы, выполняемой конструктором по умолчанию, и для присвоения нового значения строке.
- Во втором варианте требуемое значение устанавливается сразу, в точке построения.
Конечно, очень сложно априори сказать, насколько велика разница в производительности.
Требуется время, чтобы выполнить конструктор по умолчанию. Переопределение того, что инициализировало строку в последующем вызываемом операторе присваивания, также требует времени
Выполнение может никогда не достичь назначения, когда функция (из-за
return
оператор или исключение), оставленный между вызовами конструктора по умолчанию и оператора присваивания. В этом случае объект был инициализирован по умолчанию без необходимости.Реализации могут потерять производительность, чтобы гарантировать, что деструктор объекта вызывается, если выдается исключение. Если объект инициализируется в последующей области, которая так и не была достигнута, это также не требуется.
Так как:
string s; /* .... */ s = "The best is the enemy of the good.";
Включает в себя две операции: строительство и назначение
В то время как:
string s = "Voltaire";
Вовлекает только строительство.
Это эквивалентно выбору списков Инициализатора членов над Назначением в теле Конструктора.
Почему это может увеличить производительность?
Первый случай включает в себя инициализацию по умолчанию, а затем присваивание; вторая включает в себя инициализацию от значения. Инициализация по умолчанию может выполнять некоторую работу, которую впоследствии необходимо будет переделать (или даже отменить) по присваиванию, поэтому первый случай может потребовать больше работы, чем второй.
Это только так с пользовательскими типами (или даже типами STL), или это также имеет место с int, float и т. Д.?
Это только так с пользовательскими типами; и тогда это зависит от того, что на самом деле делают конструкторы и оператор присваивания. Для скалярных типов инициализация по умолчанию ничего не делает, а присваивание делает то же самое, что и инициализация из значения, поэтому обе альтернативы будут эквивалентны.
Посмотрим, что происходит в обоих случаях. В первом случае:
- конструктор по умолчанию вызывается для "s"
- оператор присваивания называется "с"
Во втором случае сначала рассмотрим, что с копией elision это эквивалентно string s("Voltaire")
, таким образом:
- конструктор c-строки называется
Логически первый подход требует, чтобы абстрактная машина выполняла больше работы. Будет ли это на самом деле переводиться в более реальный код, зависит от фактического типа и того, сколько оптимизатор может сделать. Обратите внимание, что для всех типов пользователей, кроме тривиальных, оптимизатору может потребоваться предположить, что конструктор по умолчанию имеет побочные эффекты, поэтому не может просто удалить его.
Эта дополнительная стоимость должна применяться только к пользовательским типам, так как стоимость указана в конструкторе по умолчанию. Для любого примитивного типа, такого как int, или фактически для любого с тривиальным конструктором / копией, конструктор по умолчанию не требует затрат - данные просто не будут инициализированы (когда они находятся в области действия функции).
Это хороший вопрос. Вы правы, это происходит только со сложными типами. Т.е. классы и структуры, std::string такой объект. Реальная проблема здесь связана с конструктором.
Когда объект создан, т.е.
std::string s;
Вызывается его конструктор, он, вероятно, выделяет некоторую память, выполняет инициализацию некоторых других переменных, готовится к использованию. Фактически, на этом этапе кода может быть выполнено большое количество кода.
Позже вы делаете:
s = "hello world!";
Это заставляет класс отбрасывать большую часть того, что он сделал, и готовиться заменить его содержимое новой строкой.
Это на самом деле сводится к одной операции, если вы установите значение, когда переменная определена, то есть:
std::string s = "Hello world";
на самом деле, если вы наблюдаете код в отладчике, один раз выполните другой конструктор вместо конструирования объекта, а затем отдельно установите значение. На самом деле предыдущий код работает так же, как:
std::string s("Hello world");
Я надеюсь, что это помогло немного прояснить ситуацию.
У класса есть три способа инициализации строки:
string s; // Default constructor
string s = "..."; // Default constructor followed by operator assignment
string s("..."); // Constructor with parameters passed in
Строковый класс должен выделять память. Лучше выделять его, когда он знает, сколько памяти ему нужно.