C ++ 11 позволяет внутри класса инициализировать нестатические и неконстантные члены. Что изменилось?


87

До 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? Мы просто принимаем «сложные правила компоновщика» или изменили что-то еще, что упрощает реализацию?


5
Ничего не случилось. Компиляторы стали умнее со всеми этими шаблонами только для заголовков, так что теперь это относительно простое расширение.
Öö Tiib 01

Достаточно интересно, что в моей среде IDE, когда я выбираю компиляцию до C ++ 11, мне разрешается инициализировать нестатические интегральные члены const
Дин П.

Ответы:


67

Короткий ответ заключается в том, что они сохранили компоновщик примерно таким же, за счет того, что компилятор стал еще сложнее, чем раньше.

То есть, вместо того, чтобы приводить к множеству определений для сортировки компоновщиком, он все равно дает только одно определение, и компилятор должен его разбирать.

Это также приводит к несколько более сложным правилам, которые программист тоже должен разбирать, но в основном они достаточно просты, чтобы не иметь большого значения. Дополнительные правила возникают, когда у вас есть два разных инициализатора, указанных для одного члена:

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

1
Похоже, раньше это было вполне возможно. Это только усложнило работу по написанию компилятора. Это справедливое заявление?
allyourcode

10
@allyourcode: Да и нет. Да, это усложнило написание компилятора. Но нет, потому что это также немного усложнило написание спецификации C ++.
Джерри Коффин

Есть ли разница, как инициализировать член класса: int x = 7; или int x {7} ;?
mbaros

9

Я предполагаю, что это рассуждение могло быть написано до того, как шаблоны были завершены. В конце концов, «сложные правила компоновщика», необходимые для классовых инициализаторов статических элементов, уже были необходимы для 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). Я все равно оставил его, потому что это самый сложный случай для реализации (компилятор, компоновщик). Я хочу сказать: даже этот случай не сложнее того, что уже разрешено, например, при использовании шаблонов.


Жаль, что это не получило никаких голосов, так как многие функции C ++ 11 похожи в том, что компиляторы уже включают необходимые возможности или оптимизацию.
Alex Court

@AlexCourt Я недавно написал этот ответ. Однако вопрос и ответ Джерри относятся к 2012 году. Думаю, поэтому мой ответ не получил особого внимания.
Пол Грок,

1
Это не будет соответствовать "struct A {static int s = :: ComputeSomething ();}", потому что в классе может быть инициализирована только статическая
константа

8

Теоретически So why do these inconvenient restrictions exist?...разум действителен, но его довольно легко обойти, и это именно то, что делает C ++ 11.

Когда вы включаете файл, он просто включает файл и игнорирует любую инициализацию. Члены инициализируются только при создании экземпляра класса.

Другими словами, инициализация по-прежнему привязана к конструктору, просто обозначения другие и удобнее. Если конструктор не вызывается, значения не инициализируются.

Если вызывается конструктор, значения инициализируются с инициализацией класса, если она присутствует, или конструктор может переопределить это с помощью собственной инициализации. Путь инициализации по сути тот же, то есть через конструктор.

Это видно из собственного FAQ Страуструпа по C ++ 11.


Re "Если конструктор не вызывается, значения не инициализируются": Как я могу обойти инициализацию члена Y::c3в вопросе? Насколько я понимаю, c3всегда будет инициализироваться, если нет конструктора, который отменяет значение по умолчанию, указанное в объявлении.
Питер - Восстановить Монику
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.