Стиль кодирования в конечном счете субъективен, и весьма маловероятно, что он принесет существенные преимущества в производительности. Но вот что я бы сказал, что вы получаете от либерального использования единой инициализации:
Минимизирует избыточные 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типа, и поэтому компилятор может свободно выбирать из других конструкторов.