Зачем использовать = для инициализации примитивного типа в C++?

StackOverflow https://stackoverflow.com/questions/353632

  •  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)   
{   
}   
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top