Зачем использовать = для инициализации примитивного типа в C++?
-
20-08-2019 - |
Вопрос
Там, где я работаю, люди в основном думают, что объекты лучше всего инициализировать с помощью конструкции в стиле C++ (с круглыми скобками), тогда как примитивные типы следует инициализировать с помощью оператора =:
std::string strFoo( "Foo" );
int nBar = 5;
Однако, похоже, никто не может объяснить, почему они предпочитают именно такой подход.Я это вижу std::string = "Foo";
было бы неэффективно, потому что потребовалось бы создание дополнительной копии, но что плохого в том, чтобы просто удалить =
оператор вообще и повсюду использовать круглые скобки?
Это общепринятая конвенция?Что за этим стоит?
Решение
Если вы не доказали, что это имеет значение с точки зрения производительности, я бы не стал беспокоиться о дополнительной копии с использованием оператора присваивания в вашем примере (std::string foo = "Foo";
).Я был бы очень удивлен, если бы эта копия вообще существовала, как только вы посмотрите на оптимизированный код. Я считаю, что она действительно вызовет соответствующий параметризованный конструктор.
Отвечая на ваш вопрос, да, я бы сказал, что это довольно распространенное соглашение.Традиционно люди использовали присваивание для инициализации встроенных типов, и нет веской причины менять эту традицию.Читабельность и привычка — вполне веские причины для этого соглашения, учитывая, насколько мало оно влияет на конечный код.
Другие советы
Инициализация переменных с помощью оператора = или с помощью вызова конструктора семантически одинакова, это всего лишь вопрос стиля.Я предпочитаю оператор =, поскольку он читается более естественно.
Использование оператора = обычно не генерирует дополнительную копию — он просто вызывает обычный конструктор.Однако обратите внимание, что для непримитивных типов это касается только инициализаций, которые происходят одновременно с объявлениями.Сравнивать:
std::string strFooA("Foo"); // Calls std::string(const char*) constructor
std::string strFoo = "Foo"; // Calls std::string(const char*) constructor
// This is a valid (and standard) compiler optimization.
std::string strFoo; // Calls std::string() default constructor
strFoo = "Foo"; // Calls std::string::operator = (const char*)
Если у вас есть нетривиальные конструкторы по умолчанию, последняя конструкция может быть немного более неэффективной.
А стандарт С++, раздел 8.5, пункт 14 гласит:
В противном случае (т. е. для остальных случаев инициализации копирования) создается временный объект.Определенные пользователем последовательности преобразования, которые могут преобразовать исходный тип в целевой тип или его производный класс, перечислены (13.3.1.4), и лучшая из них выбирается посредством разрешения перегрузки (13.3).Выбранное пользовательское преобразование, выбранное, вызвано для преобразования выражения инициализатора во временное, тип которого-тип, возвращаемый вызовом функции преобразования, определенной пользователем, с помощью CV-квокаливателей типа назначения.Если преобразование невозможно выполнить или оно неоднозначно, инициализация имеет неверный формат.Инициализированный объект затем прямо инициализируется от временного в соответствии с приведенными выше правилами.87) В некоторых случаях реализации разрешено исключать временное путем непосредственной инициализации объекта;см. 12.2.
Часть раздела 12.2 гласит:
Даже если создание временного объекта исключено, все семантические ограничения должны соблюдаться так, как если бы временный объект был создан.[Пример:даже если конструктор копирования не вызывается, все семантические ограничения, такие как доступность (11), должны быть удовлетворены.]
Я просто почувствовал потребность в еще одном глупом посте.
string str1 = "foo";
называется копирование-инициализация, потому что компилятор, если он не удаляет временные объекты, делает следующее:
string str1(string("foo"));
помимо проверки того, что используемый конструктор преобразования является неявным.Фактически, все неявные преобразования определяются стандартом с точки зрения инициализации копирования.Говорят, что неявное преобразование типа U в тип T допустимо, если
T t = u; // u of type U
действует.
В отличие от этого,
string str1("foo");
делает именно то, что написано и называется прямая инициализация.Он также работает с явными конструкторами.
Кстати, вы можете отключить скрытие временных объектов с помощью -fno-elide-constructors:
-fno-elide-constructors
The C++ standard allows an implementation to omit creating a temporary which
is only used to initialize another object of the same type. Specifying this
option disables that optimization, and forces G++ to call the copy constructor
in all cases.
В стандарте говорится, что практически нет разницы между
T a = u;
и
T a(u);
если T и тип u являются примитивными типами.Таким образом, вы можете использовать обе формы.Я думаю, что именно стиль заставляет людей использовать первую форму, а не вторую.
Некоторые люди могут использовать первое в какой-то ситуации, потому что хотят устранить неоднозначность объявления:
T u(v(a));
может показаться кому-то определением переменной u
который инициализируется с использованием временного типа v
который получает параметр для своего конструктора с именем a
.Но на самом деле компилятор с этим делает следующее:
T u(v a);
Он создает объявление функции, которая принимает аргумент типа v
, и с параметром под названием a
.Так люди делают
T u = v(a);
чтобы устранить двусмысленность, хотя они могли бы сделать
T u((v(a)));
Кроме того, поскольку параметры функции никогда не заключаются в круглые скобки, компилятор также будет считать их определением переменной, а не объявлением функции :)
Вероятно, вы найдете этот код, например
std::string strFoo = "Foo";
избегает дополнительного копирования и компилируется в тот же код (вызов конструктора с одним аргументом), что и код с круглыми скобками.
С другой стороны, бывают случаи, когда один должен используйте круглые скобки, например список инициализации членов конструктора.
Я думаю, что использование = или круглых скобок для создания локальных переменных во многом является вопросом личного выбора.
Ну кто знает что они Подумайте, но я также предпочитаю = для примитивных типов, главным образом потому, что они не являются объектами и потому что это «обычный» способ их инициализации.
Это вопрос стиля.Даже утверждение, что "std::string = "Foo";было бы неэффективно, поскольку потребовало бы создания дополнительной копии», это неверно.Эта «лишняя копия» удаляется компилятором.
Я считаю, что это скорее привычка: очень немногие объекты можно инициализировать с помощью =, строка — один из них.Это также способ сделать то, что вы сказали: «использовать круглые скобки везде (что язык позволяет вам их использовать)»
Один аргумент, который можно было бы привести:
std::string foo("бар");
Это то, что он сохраняет все то же самое, даже если количество аргументов меняется, т.е.:
std::string foo("бар", 5);
Не работает со знаком «=».
Другое дело, что для многих объектов знак '=' кажется неестественным, например, скажем, у вас есть класс Array, аргумент которого задает длину:
Массив arr = 5;
Это неприятно, поскольку мы создаем массив не со значением 5, а с длиной 5:
Массив arr(5);
выглядит более естественно, поскольку вы создаете объект с заданным параметром, а не просто копируете значение.
Но затем, чтобы еще больше вас запутать, вы инициализируете примитивы в списке инициализации, используя синтаксис объекта.
foo::foo()
,anInt(0)
,aFloat(0.0)
{
}