Там, где я работаю, люди в основном думают, что объекты лучше всего инициализировать, используя конструкцию в стиле C++ (со скобками), тогда как примитивные типы следует инициализировать с помощью оператора =:
std::string strFoo( "Foo" );
int nBar = 5;
Однако, кажется, никто не может объяснить, почему они предпочитают именно так. Я вижу, что std::string = "Foo"; был бы неэффективен, потому что он потребовал бы дополнительной копии, но что плохого в том, чтобы просто полностью исключить оператор = и везде использовать круглые скобки?
Это обычная конвенция? Что за этим стоит?





Это вопрос стиля. Даже утверждение, что «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. ]
однако компилятору разрешено исключить временное, которое создается и передается конструктору копирования. так что вы можете не заметить временное создание.
Статья Херба Саттера о «прямой инициализации» и «инициализации копии»: gotw.ca/gotw/036.htm
@litb: компилятору разрешено полностью оптимизировать конструкцию временного объекта и просто вызвать обычный конструктор.
Однако это не требуется, поэтому во втором случае выше разрешено вызывать конструктор копирования, а также конструктор (const char *), а затем деструктор временного объекта. Но вы бы пожаловались поставщику компилятора, если бы это случилось ...
он также должен проверить видимость конструктора копирования. если он недоступен, форма = действительно недействительна :)
Вы, вероятно, найдете этот код, например
std::string strFoo = "Foo";
позволит избежать создания дополнительной копии и компилируется в тот же код (вызов конструктора с одним аргументом), что и код со скобками.
С другой стороны, бывают случаи, когда один должен использует круглые скобки, например, список инициализации члена конструктора.
Я думаю, что использование = или круглых скобок для создания локальных переменных в значительной степени является делом личного выбора.
Что ж, кто знает, что думает Oни, но я также предпочитаю = для примитивных типов, главным образом потому, что они не являются объектами, и потому что это «обычный» способ их инициализации.
В C++ объекты примитивного типа являются объектами, а не объектами класса.
какие? с каких пор? Я говорю об объектах в смысле ООП. Примитивные типы C++ определенно не являются объектами.
да. все, кроме ссылок, функций и типов void, являются объектами.
Я имел в виду определение «объекта» в стандарте C++. Думаю, я согласен с тем, что они в лучшем случае вырожденные объекты с точки зрения ООП: их единственные «члены» - это операторы. Но я не вижу никакого отношения к тому, как они инициализируются: я думаю, что это просто потому, что это «обычный способ», унаследованный от C.
Если вы не доказали, что это имеет значение с точки зрения производительности, я бы не стал беспокоиться о дополнительной копии с помощью оператора присваивания в вашем примере (std::string foo = "Foo";). Я был бы очень удивлен, если бы эта копия вообще существовала, как только вы посмотрите на оптимизированный код, я считаю, что это действительно вызовет соответствующий параметризованный конструктор.
Отвечая на ваш вопрос, да, я бы сказал, что это довольно распространенное соглашение. Традиционно люди использовали присваивание для инициализации встроенных типов, и нет веских причин менять традицию. Удобочитаемость и привычка - вполне веские причины для этого соглашения, учитывая, как мало оно влияет на окончательный код.
Ах! Да, именно так: в C оператор = был единственным способом, так что он по-прежнему «кажется правильным» для старожилов. Спасибо.
Я считаю, что это скорее привычка, очень мало объектов можно инициализировать с помощью =, строка является одним из них. Это также способ сделать то, что вы сказали: «везде использовать круглые скобки (что язык позволяет вам их использовать)».
Я просто почувствовал потребность в еще одном глупом маленьком посте.
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) была введена в стандарт для использования в списках инициализации и для примитивных типов эквивалентна.
Один аргумент, который можно было бы привести:
std :: string foo ("бар");
В том, что он сохраняет то же самое, даже если количество аргументов изменяется, то есть:
std :: string foo ("полоса", 5);
Не работает со знаком "=".
Другое дело, что для многих объектов знак '=' кажется неестественным, например, если у вас есть класс Array, в котором аргумент дает длину:
Массив arr = 5;
Это нехорошо, поскольку мы создаем массив не со значением 5, а с длиной 5:
Массив arr (5);
кажется более естественным, поскольку вы создаете объект с заданным параметром, а не просто копируете значение.
Ключевое слово «explicit» используется именно для этой цели - чтобы убедиться, что «Array arr = 5» не компилируется. Вы должны сказать «Array arr (5)», если конструктор Array (int) объявлен явно.
Но чтобы запутать вас еще больше, вы инициализируете примитивы в списке инициализации, используя синтаксис объекта.
foo::foo()
,anInt(0)
,aFloat(0.0)
{
}
using = создаст временную строку и инициализирует объект, используя эту временную строку с конструктором копирования. (это причина, по которой форма называется инициализацией копии, а строка o ("foo") называется прямой инициализацией). другая разница в том, что = "foo" требует неявного ctor.