C ++ 11 дозволяє ініціалізацію в класі нестатичних та неконстантних членів. Що змінилося?


87

До C ++ 11 ми могли виконувати ініціалізацію в класі лише для статичних членів const цілого або типу перелічення. Страуструп обговорює це у своїх запитаннях щодо 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 ++ 11 послаблює ці обмеження, дозволяючи ініціалізацію в класі нестатичних членів (§12.6.2 / 8):

У не делегуючому конструкторі, якщо даний нестатичний член даних або базовий клас не позначається мем-ініціалізатором-ідентифікатором (включаючи випадок, коли немає списку мем-ініціалізаторів, оскільки конструктор не має ініціатора ctor ) і сутність не є віртуальним базовим класом абстрактного класу (10.4), тоді

  • якщо сутність є нестатичним членом даних, який має ініціалізатор фігурної дужки або рівного , сутність ініціалізується, як зазначено в 8.5;
  • інакше, якщо сутність є варіантом (9.5), ініціалізація не виконується;
  • в іншому випадку сутність ініціалізується за замовчуванням (8.5).

Розділ 9.4.2 також дозволяє ініціалізацію в класі неконстантних статичних членів, якщо вони позначені значком constexpr специфікатором.

То що трапилося з причинами обмежень, які ми мали в C ++ 03? Ми просто приймаємо "складні правила компонування" чи щось інше змінилося, що полегшує це впровадження?


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

Цікаво, що в моїй IDE, коли я вибираю попередню компіляцію до C ++ 11, мені дозволяється ініціалізувати нестатичні константні інтегральні члени
Dean P,

Відповіді:


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). Я все-таки залишив це, тому що це IMO - найважчий випадок для реалізації (компілятор, компонувальник). Я хочу сказати: навіть цей випадок не є складнішим за те, що вже дозволено, наприклад, при використанні шаблонів.


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

@AlexCourt Я нещодавно написав цю відповідь. Питання та відповідь Джеррі - з 2012 року. Тож, думаю, саме тому моїй відповіді не приділили особливої ​​уваги.
Paul Groke

1
Це не буде відповідати "struct A {static int s = :: ComputeSomething ();}", оскільки в класі можна ініціалізувати лише статичний const
PapaDiHatti

8

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

Коли ви включаєте файл, він просто включає файл і не враховує будь-яку ініціалізацію. Члени ініціалізуються лише тоді, коли ви створюєте екземпляр класу.

Іншими словами, ініціалізація все ще пов’язана з конструктором, просто позначення відрізняються і є зручнішими. Якщо конструктор не викликається, значення не ініціалізуються.

Якщо викликається конструктор, значення ініціалізуються ініціалізацією класу, якщо вона присутня, або конструктор може замінити це власною ініціалізацією. Шлях ініціалізації є по суті однаковим, тобто через конструктор.

Це видно з власних поширених запитань Stroustrup про C ++ 11.


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