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

Там, где я работаю, люди в основном думают, что объекты лучше всего инициализировать, используя конструкцию в стиле C++ (со скобками), тогда как примитивные типы следует инициализировать с помощью оператора =:

std::string strFoo( "Foo" );
int nBar = 5;

Однако, кажется, никто не может объяснить, почему они предпочитают именно так. Я вижу, что std::string = "Foo"; был бы неэффективен, потому что он потребовал бы дополнительной копии, но что плохого в том, чтобы просто полностью исключить оператор = и везде использовать круглые скобки?

Это обычная конвенция? Что за этим стоит?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
0
2 907
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Это вопрос стиля. Даже утверждение, что «std :: string =« 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*)

Когда у вас есть нетривиальные конструкторы по умолчанию, последняя конструкция может быть немного более неэффективной.

Стандарт C++, раздел 8.5, параграф 14 гласит:

Otherwise (i.e., for the remaining copy-initialization cases), a temporary is created. User-defined conversion sequences that can convert from the source type to the destination type or a derived class thereof are enumerated (13.3.1.4), and the best one is chosen through overload resolution (13.3). The user-defined conversion so selected is called to convert the initializer expression into a temporary, whose type is the type returned by the call of the user-defined conversion function, with the cv-qualifiers of the destination type. If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The object being initialized is then direct-initialized from the temporary according to the rules above.87) In certain cases, an implementation is permitted to eliminate the temporary by initializing the object directly; see 12.2.

В части раздела 12.2 говорится:

Even when the creation of the temporary object is avoided, all the semantic restrictions must be respected as if the temporary object was created. [Example: even if the copy constructor is not called, all the semantic restrictions, such as accessibility (11), shall be satisfied. ]

using = создаст временную строку и инициализирует объект, используя эту временную строку с конструктором копирования. (это причина, по которой форма называется инициализацией копии, а строка o ("foo") называется прямой инициализацией). другая разница в том, что = "foo" требует неявного ctor.

Johannes Schaub - litb 09.12.2008 21:12

однако компилятору разрешено исключить временное, которое создается и передается конструктору копирования. так что вы можете не заметить временное создание.

Johannes Schaub - litb 09.12.2008 21:13

Статья Херба Саттера о «прямой инициализации» и «инициализации копии»: gotw.ca/gotw/036.htm

Michael Burr 09.12.2008 21:26

@litb: компилятору разрешено полностью оптимизировать конструкцию временного объекта и просто вызвать обычный конструктор.

Martin York 09.12.2008 21:29

Однако это не требуется, поэтому во втором случае выше разрешено вызывать конструктор копирования, а также конструктор (const char *), а затем деструктор временного объекта. Но вы бы пожаловались поставщику компилятора, если бы это случилось ...

Steve Jessop 09.12.2008 21:40

он также должен проверить видимость конструктора копирования. если он недоступен, форма = действительно недействительна :)

Johannes Schaub - litb 09.12.2008 21:46

Вы, вероятно, найдете этот код, например

std::string strFoo = "Foo";

позволит избежать создания дополнительной копии и компилируется в тот же код (вызов конструктора с одним аргументом), что и код со скобками.

С другой стороны, бывают случаи, когда один должен использует круглые скобки, например, список инициализации члена конструктора.

Я думаю, что использование = или круглых скобок для создания локальных переменных в значительной степени является делом личного выбора.

Что ж, кто знает, что думает Oни, но я также предпочитаю = для примитивных типов, главным образом потому, что они не являются объектами, и потому что это «обычный» способ их инициализации.

В C++ объекты примитивного типа являются объектами, а не объектами класса.

Steve Jessop 09.12.2008 21:06

какие? с каких пор? Я говорю об объектах в смысле ООП. Примитивные типы C++ определенно не являются объектами.

hasen 09.12.2008 21:42

да. все, кроме ссылок, функций и типов void, являются объектами.

Johannes Schaub - litb 09.12.2008 21:59

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

Steve Jessop 10.12.2008 04:25
Ответ принят как подходящий

Если вы не доказали, что это имеет значение с точки зрения производительности, я бы не стал беспокоиться о дополнительной копии с помощью оператора присваивания в вашем примере (std::string foo = "Foo";). Я был бы очень удивлен, если бы эта копия вообще существовала, как только вы посмотрите на оптимизированный код, я считаю, что это действительно вызовет соответствующий параметризованный конструктор.

Отвечая на ваш вопрос, да, я бы сказал, что это довольно распространенное соглашение. Традиционно люди использовали присваивание для инициализации встроенных типов, и нет веских причин менять традицию. Удобочитаемость и привычка - вполне веские причины для этого соглашения, учитывая, как мало оно влияет на окончательный код.

Ах! Да, именно так: в C оператор = был единственным способом, так что он по-прежнему «кажется правильным» для старожилов. Спасибо.

Tommy Herbert 09.12.2008 22:20

Я считаю, что это скорее привычка, очень мало объектов можно инициализировать с помощью =, строка является одним из них. Это также способ сделать то, что вы сказали: «везде использовать круглые скобки (что язык позволяет вам их использовать)».

Я просто почувствовал потребность в еще одном глупом маленьком посте.

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)));

тоже, так как параметры функции никогда не заключаются в круглые скобки, компилятор будет читать это как определение переменной, а не как объявление функции :)

Исторический: в случае примитивных типов T a = u долгое время была единственной допустимой формой. Вторая форма T a (u) была введена в стандарт для использования в списках инициализации и для примитивных типов эквивалентна.

David Rodríguez - dribeas 10.12.2008 00:25

Один аргумент, который можно было бы привести:

std :: string foo ("бар");

В том, что он сохраняет то же самое, даже если количество аргументов изменяется, то есть:

std :: string foo ("полоса", 5);

Не работает со знаком "=".

Другое дело, что для многих объектов знак '=' кажется неестественным, например, если у вас есть класс Array, в котором аргумент дает длину:

Массив arr = 5;

Это нехорошо, поскольку мы создаем массив не со значением 5, а с длиной 5:

Массив arr (5);

кажется более естественным, поскольку вы создаете объект с заданным параметром, а не просто копируете значение.

Ключевое слово «explicit» используется именно для этой цели - чтобы убедиться, что «Array arr = 5» не компилируется. Вы должны сказать «Array arr (5)», если конструктор Array (int) объявлен явно.

Adam Rosenfield 09.12.2008 21:55

Но чтобы запутать вас еще больше, вы инициализируете примитивы в списке инициализации, используя синтаксис объекта.

foo::foo()   
  ,anInt(0)   
  ,aFloat(0.0)   
{   
}   

Другие вопросы по теме