До C ++ 11 мы могли выполнять инициализацию в классе только для статических константных членов целочисленного типа или типа перечисления. Страуструп обсуждает это в своих C ++ FAQ, приведем следующий пример:
class Y {
const int c3 = 7; // error: not static
static int c4 = 7; // error: not const
static const float c5 = 7; // error: not integral
};
И следующие рассуждения:
Так почему же существуют эти неудобные ограничения? Класс обычно объявляется в файле заголовка, а файл заголовка обычно включается во многие единицы перевода. Однако, чтобы избежать сложных правил компоновщика, C ++ требует, чтобы у каждого объекта было уникальное определение. Это правило было бы нарушено, если бы C ++ допускал определение сущностей в классе, которые должны были храниться в памяти как объекты.
Однако C ++ 11 ослабляет эти ограничения, разрешая инициализацию в классе нестатических членов (§12.6.2 / 8):
В не делегирующем конструкторе, если данный нестатический элемент данных или базовый класс не обозначен мем-инициализатор-идентификатор (включая случай, когда нет мем-инициализатора-лист потому что конструктор не имеет т е р-инициализатор) и сущность не является виртуальным базовым классом абстрактного класса (10.4), то
- если объект является нестатическим членом данных, который имеет скобки или равно-инициализатор, объект инициализируется, как указано в 8.5;
- в противном случае, если объект является вариантом члена (9.5), инициализация не выполняется;
- в противном случае объект инициализируется по умолчанию (8.5).
Раздел 9.4.2 также разрешает инициализацию в классе неконстантных статических членов, если они помечены constexpr
спецификатор.
Так что же случилось с причинами ограничений, которые были у нас в C ++ 03? Должны ли мы просто принять «сложные правила компоновщика» или что-то еще изменилось, что облегчает реализацию?
Короткий ответ: они сохранили компоновщик примерно таким же, за счет того, что компилятор стал еще сложнее, чем раньше.
Т.е. вместо того, чтобы это приводило к множественным определениям для сортировки компоновщиком, это все равно приводит только к одному определению, и компилятор должен сортировать его.
Это также приводит к несколько более сложным правилам для программист чтобы разобраться, но это в основном достаточно просто, что это не имеет большого значения. Дополнительные правила вступают в силу, когда для одного члена заданы два разных инициализатора:
class X {
int a = 1234;
public:
X() = default;
X(int z) : a(z) {}
};
Теперь дополнительные правила в этой точке имеют дело с тем, какое значение используется для инициализации a
когда вы используете конструктор не по умолчанию. Ответ на этот вопрос довольно прост: если вы используете конструктор, который не указывает никакого другого значения, тогда 1234
будет использоваться для инициализации a
— но если вы используете конструктор, который задает какое-то другое значение, то 1234
в основном игнорируется.
Например:
#include <iostream>
class X {
int a = 1234;
public:
X() = default;
X(int z) : a(z) {}
friend std::ostream &operator<<(std::ostream &os, X const &x) {
return os << x.a;
}
};
int main() {
X x;
X y{5678};
std::cout << x << "\n" << y;
return 0;
}
Результат:
1234
5678
Я думаю, что аргументация могла быть написана до того, как шаблоны были доработаны. В конце концов, «сложные правила компоновщика», необходимые для инициализаторов в классе статических членов, были / были необходимы C ++ 11 для поддержки статических членов шаблонов.
Рассматривать
struct A { static int s = ::ComputeSomething(); }; // NOTE: This isn't even allowed,
// thanks @Kapil for pointing that out
// vs.
template <class T>
struct B { static int s; }
template <class T>
int B<T>::s = ::ComputeSomething();
// or
template <class T>
void Foo()
{
static int s = ::ComputeSomething();
s++;
std::cout << s << "\n";
}
Проблема для компилятора одинакова во всех трех случаях: в каком блоке перевода он должен давать определение s
и код, необходимый для его инициализации? Простое решение — испустить его везде и позволить компоновщику разобраться. Вот почему линкеры уже поддерживали такие вещи, как __declspec(selectany)
, Просто было бы невозможно реализовать C ++ 03 без него. И именно поэтому не было необходимости расширять компоновщик.
Проще говоря: я думаю, что аргументация, приведенная в старом стандарте, просто неверна.
ОБНОВИТЬ
Как отметил Капил, мой первый пример даже не разрешен в текущем стандарте (C ++ 14). Я все равно оставил его, потому что это IMO — самый сложный случай для реализации (компилятор, компоновщик). Моя точка зрения: даже тот дело не сложнее, чем то, что уже разрешено, например при использовании шаблонов.
Теоретически So why do these inconvenient restrictions exist?...
причина верна, но ее можно легко обойти, и это именно то, что делает C ++ 11.
Когда ты включают файл, он просто включает файл и игнорирует любую инициализацию. Члены инициализируются только тогда, когда вы иллюстрировать примерами класс.
Другими словами, инициализация по-прежнему связана с конструктором, просто обозначения отличаются и более удобны. Если конструктор не вызывается, значения не инициализируются.
Если вызывается конструктор, значения инициализируются с помощью инициализации в классе, если она есть, или конструктор может переопределить это с помощью собственной инициализации. Путь инициализации по сути такой же, то есть через конструктор.
Это видно из собственного Страуструпа Часто задаваемые вопросы на C ++ 11.