Например, я не могу написать это:
class A
{
vector<int> v(12, 1);
};
Я могу только написать это:
class A
{
vector<int> v1{ 12, 1 };
vector<int> v2 = vector<int>(12, 1);
};
Что учитывает различия в языке С++ 11?
Например, я не могу написать это:
class A
{
vector<int> v(12, 1);
};
Я могу только написать это:
class A
{
vector<int> v1{ 12, 1 };
vector<int> v2 = vector<int>(12, 1);
};
Что учитывает различия в языке С++ 11?
Одна из возможных причин заключается в том, что в скобках мы можем вернуться к наиболее неприятному анализу в кратчайшие сроки. Рассмотрим два типа ниже:
struct foo {};
struct bar
{
bar(foo const&) {}
};
Теперь у вас есть член данных типа bar
, который вы хотите инициализировать, поэтому вы определяете его как
struct A
{
bar B(foo());
};
Но то, что вы сделали выше, объявляет функцию с именем B
, которая возвращает объект bar
по значению и принимает один аргумент, что функция, имеющая подпись foo()
(возвращает a foo
и doesn 't принимать какие-либо аргументы).
Судя по количеству и частоте вопросов, заданных в StackOverflow, которые касаются этой проблемы, это то, что большинство программистов на C++ находят удивительным и неинтуитивным. Добавление нового синтаксиса синтаксиса brace-or-equal-initializer позволило избежать этой двусмысленности и начать с чистого листа, что, вероятно, является причиной того, что комитет С++ решил сделать это.
bar B{foo{}};
bar B = foo();
Обе строки выше объявляют объект с именем B
типа bar
, как и ожидалось.
Помимо догадок выше, я хотел бы указать, что вы делаете две совершенно разные вещи в приведенном выше примере.
vector<int> v1{ 12, 1 };
vector<int> v2 = vector<int>(12, 1);
Первая строка инициализирует v1
вектору, который содержит два элемента: 12
и 1
. Второй создает вектор v2
, содержащий элементы 12
, каждый из которых инициализируется символом 1
.
Будьте осторожны с этим правилом - если тип определяет конструктор, который принимает initializer_list<T>
, тогда этот конструктор всегда считается первым, когда инициализатор для этого типа является скоординированным списком инициализации. Другие конструкторы будут рассмотрены только в том случае, если один, принимающий initializer_list
, не является жизнеспособным.
Обоснование этого выбора явно упоминается в связанном предложении для инициализаторов нестационарных элементов данных:
Проблема, поднятая в Kona относительно объема идентификаторов:
В ходе обсуждения в Основной рабочей группе на заседании 07 сентября в Коне возник вопрос об объеме идентификаторов в инициализаторе. Мы хотим разрешить класс scope с возможностью прямого поиска; или мы хотим потребовать, чтобы инициализаторы были четко определены в точке, в которой они были проанализированы?
Желательно:
Мотивация для поиска в классе-классе заключается в том, что он мог поместить что-либо в инициализатор нестатических данных, который мы могли бы поместить в mem-инициализатор без существенного изменения семантики (по модулю прямой инициализации и инициализации копирования ):
int x();
struct S {
int i;
S() : i(x()) {} // currently well-formed, uses S::x()
// ...
static int x();
};
struct T {
int i = x(); // should use T::x(), ::x() would be a surprise
// ...
static int x();
};
Проблема 1:
К сожалению, это делает инициализаторы формы "(выражение-список)" неоднозначными в момент разбора объявления:
struct S {
int i(x); // data member with initializer
// ...
static int x;
};
struct T {
int i(x); // member function declaration
// ...
typedef int x;
};
Одно из возможных решений заключается в том, чтобы полагаться на существующее правило, которое, если объявление может быть объектом или функцией, тогда его функция:
struct S {
int i(j); // ill-formed...parsed as a member function,
// type j looked up but not found
// ...
static int j;
};
Аналогичным решением было бы применить другое существующее правило, которое в настоящее время используется только в шаблонах, если T может быть типом или чем-то другим, то это нечто другое; и мы можем использовать "typename", если мы действительно имеем в виду тип:
struct S {
int i(x); // unabmiguously a data member
int j(typename y); // unabmiguously a member function
};
Оба этих решения вводят тонкости, которые многие пользователи могут недопонимать (о чем свидетельствует множество вопросов о comp.lang.С++ о том, почему "int i();" в области блока не объявляет инициализацию по умолчанию int).
Решение, предлагаемое в этой статье, состоит в том, чтобы разрешить только инициализаторы форм "= initializer-clause" и "{initializer-list}" . Это решает проблему двусмысленности в большинстве случаях, например:
HashingFunction hash_algorithm{"MD5"};
Здесь мы не могли использовать форму =, потому что конструктор HasningFunctions является явным. В особенно сложных случаях, тип, возможно, придется упомянуть дважды. Рассмотрим:
vector<int> x = 3; // error: the constructor taking an int is explicit
vector<int> x(3); // three elements default-initialized
vector<int> x{3}; // one element with the value 3
В этом случае мы должны выбрать между двумя альтернативами, используя соответствующие обозначения:
vector<int> x = vector<int>(3); // rather than vector<int> x(3);
vector<int> x{3}; // one element with the value 3
Проблема 2:
Другая проблема заключается в том, что, поскольку мы не предлагаем никаких изменений в правилах инициализации статических элементов данных, добавление статического ключевого слова может привести к неправильному формированию правильно сформированного инициализатора:
struct S {
const int i = f(); // well-formed with forward lookup
static const int j = f(); // always ill-formed for statics
// ...
constexpr static int f() { return 0; }
};
Проблема 3:
Третья проблема заключается в том, что поиск по классам может превратить ошибку времени компиляции во временную ошибку:
struct S {
int i = j; // ill-formed without forward lookup, undefined behavior with
int j = 3;
};
(Если не пойман компилятором, я мог бы быть проиндексирован с помощью значения undefined j.)
Предложение:
У CWG был опрос соломы от 6 до 3 в Kona в пользу поиска класса; и это то, что предлагает этот документ, с инициализаторами для нестатических членов данных, ограниченными формами "= initializer-clause" и "{initializer-list".
Мы считаем:
Проблема 1: эта проблема не возникает, поскольку мы не предлагаем нотацию(). Обозначения инициализатора = и {} не страдают от этой проблемы.
Проблема 2: добавление ключевого слова static делает ряд различий, это наименьшее из них.
Проблема 3: это не новая проблема, но такая же проблема с порядком инициализации, которая уже существует с инициализаторами конструктора.