Стиль кодирования в конечном счете субъективен, и весьма маловероятно, что он принесет существенные преимущества в производительности. Но вот что я бы сказал, что вы получаете от либерального использования единой инициализации:
Минимизирует избыточные Typenames
Учтите следующее:
vec3 GetValue()
{
return vec3(x, y, z);
}
Зачем мне печатать vec3
дважды? Есть ли смысл в этом? Компилятор хорошо знает, что возвращает функция. Почему я не могу просто сказать "вызвать конструктор того, что я возвращаю с этими значениями, и вернуть его?" При равномерной инициализации я могу:
vec3 GetValue()
{
return {x, y, z};
}
Все работает.
Еще лучше для аргументов функции. Учти это:
void DoSomething(const std::string &str);
DoSomething("A string.");
Это работает без необходимости вводить типовое имя, потому что std::string
знает, как создать себя из const char*
неявного. Замечательно. Но что, если эта строка пришла, скажем, RapidXML. Или строка Lua. То есть, допустим, я на самом деле знаю длину строки впереди. std::string
Конструктор , который принимает const char*
придется взять длину строки , если я просто пропускать const char*
.
Существует перегрузка, которая явно принимает длину. Но использовать его, я должен был бы сделать это: DoSomething(std::string(strValue, strLen))
. Почему там есть дополнительное имя типа? Компилятор знает, что это за тип. Так же, как и с auto
, мы можем избежать дополнительных названий типов:
DoSomething({strValue, strLen});
Это просто работает. Нет названий, нет суеты, ничего. Компилятор делает свою работу, код короче, и все счастливы.
Конечно, есть аргументы в пользу того, что первая версия ( DoSomething(std::string(strValue, strLen))
) более разборчива. То есть очевидно, что происходит и кто что делает. Это правда, до некоторой степени; понимание единого кода, основанного на инициализации, требует рассмотрения прототипа функции. Это та же самая причина, по которой некоторые говорят, что вы никогда не должны передавать параметры по неконстантной ссылке: чтобы вы могли видеть на сайте вызовов, если значение изменяется.
Но то же самое можно сказать и для auto
; Знание того, что вы получаете, auto v = GetSomething();
требует рассмотрения определения GetSomething
. Но это не помешало auto
использовать его с почти безрассудным отказом, как только вы получите к нему доступ. Лично я думаю, что все будет хорошо, когда ты к этому привыкнешь. Особенно с хорошей IDE.
Никогда не получай самый волнующий разбор
Вот код
class Bar;
void Func()
{
int foo(Bar());
}
Поп-викторина: что это foo
? Если вы ответили «переменная», вы ошибаетесь. На самом деле это прототип функции, которая принимает в качестве параметра функцию, которая возвращает a Bar
, а foo
возвращаемое значение функции - int.
Это называется C ++ "Most Vexing Parse", потому что это не имеет абсолютно никакого смысла для человека. Но правила C ++, к сожалению, требуют этого: если это можно интерпретировать как прототип функции, то так и будет . Проблема в том Bar()
, это может быть одной из двух вещей. Это может быть тип с именем Bar
, что означает, что он создает временный. Или это может быть функция, которая не принимает параметров и возвращает a Bar
.
Равномерная инициализация не может быть интерпретирована как прототип функции:
class Bar;
void Func()
{
int foo{Bar{}};
}
Bar{}
всегда создает временный. int foo{...}
всегда создает переменную.
Есть много случаев, когда вы хотите использовать, Typename()
но просто не можете из-за правил синтаксического анализа в C ++. С Typename{}
, нет никакой двусмысленности.
Причины не
Единственная реальная сила, от которой вы отказываетесь, - это сужение. Вы не можете инициализировать меньшее значение большим значением с равномерной инициализацией.
int val{5.2};
Это не скомпилируется. Вы можете сделать это со старомодной инициализацией, но не с равномерной инициализацией.
Это было сделано частично для того, чтобы списки инициализаторов действительно работали. В противном случае было бы много неоднозначных случаев в отношении типов списков инициализатора.
Конечно, некоторые могут утверждать, что такой код не заслуживает компиляции. Я лично согласен; сужение очень опасно и может привести к неприятному поведению. Вероятно, лучше всего уловить эти проблемы на ранней стадии компиляции. По крайней мере, сужение предполагает, что кто-то не слишком задумывается о коде.
Обратите внимание, что компиляторы, как правило, предупреждают вас об этом, если ваш уровень предупреждения высок. Так что, на самом деле, все, что делает, это превращает предупреждение в вынужденную ошибку. Некоторые могут сказать, что вы все равно должны это делать;)
Есть еще одна причина не:
std::vector<int> v{100};
Что это делает? Он может создать vector<int>
сотню созданных по умолчанию предметов. Или это может создать vector<int>
с 1 предметом, чья ценность 100
. Оба теоретически возможны.
В действительности это делает последнее.
Почему? Списки инициализаторов используют тот же синтаксис, что и при равномерной инициализации. Таким образом, должны быть некоторые правила, чтобы объяснить, что делать в случае двусмысленности. Правило довольно простое: если компилятор может использовать конструктор списка инициализатора со списком, инициализированным фигурной скобкой, то он будет . Так как vector<int>
имеет конструктор списка инициализаторов, который принимает initializer_list<int>
, и {100} может быть допустимым initializer_list<int>
, поэтому он должен быть .
Чтобы получить конструктор размеров, вы должны использовать ()
вместо {}
.
Обратите внимание, что если бы это было vector
что-то, что не может быть преобразовано в целое число, этого бы не произошло. Инициализатор initializer_list не подходит для конструктора списка инициализаторов этого vector
типа, и поэтому компилятор может свободно выбирать из других конструкторов.