До C ++ 11 мы могли выполнять инициализацию внутри класса только для статических констант целочисленного или перечислимого типа. Страуструп обсуждает это в своем FAQ по C ++ , приводя следующий пример:
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):
В конструкторе без делегирования, если данный нестатический член данных или базовый класс не обозначен идентификатором mem-initializer-id (включая случай, когда нет mem-initializer-list, потому что конструктор не имеет инициализатора ctor ) и объект не является виртуальным базовым классом абстрактного класса (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). Я все равно оставил его, потому что это самый сложный случай для реализации (компилятор, компоновщик). Я хочу сказать: даже этот случай не сложнее того, что уже разрешено, например, при использовании шаблонов.
источник
Теоретически
So why do these inconvenient restrictions exist?...
разум действителен, но его довольно легко обойти, и это именно то, что делает C ++ 11.Когда вы включаете файл, он просто включает файл и игнорирует любую инициализацию. Члены инициализируются только при создании экземпляра класса.
Другими словами, инициализация по-прежнему привязана к конструктору, просто обозначения другие и удобнее. Если конструктор не вызывается, значения не инициализируются.
Если вызывается конструктор, значения инициализируются с инициализацией класса, если она присутствует, или конструктор может переопределить это с помощью собственной инициализации. Путь инициализации по сути тот же, то есть через конструктор.
Это видно из собственного FAQ Страуструпа по C ++ 11.
источник
Y::c3
в вопросе? Насколько я понимаю,c3
всегда будет инициализироваться, если нет конструктора, который отменяет значение по умолчанию, указанное в объявлении.