#c #constructor #order-of-execution #member-initialization #initialization-order
#c #constructor #порядок выполнения #элемент-инициализация #порядок инициализации
Вопрос:
У меня есть следующий класс стека.
class Stack{
public:
int size;
int* x;
Stack() : size(10), x(new int[10]) {}
Stack(const Stackamp; s) : x(new int[size=s.size]) {}
};
Обратите внимание на присвоение в конструкторе копирования. Код работает, компилируется нормально, и компилятор (gcc) не жалуется даже на -Wall -Wextra
флаги. Автоматически ли компилятор перезаписывает компилятор для этого?
Stack(const Stackamp; s) : size(s.size), x(new int[size]) {}
Или есть какая-то другая магия? Я заметил, что при изменении порядка определения компилятор жалуется на инициализацию не по порядку. Итак, я предполагаю, что это тот случай, о котором я упоминал. Я ничего не смог найти в документации, и вывод ASM мне тоже не помогает.
Ответ №1:
Автоматически ли компилятор перезаписывает компилятор для этого?
Stack(const Stackamp; s) : size(s.size), x(new int[size]) {}
Нет.
Stack(const Stackamp; s) : x(new int[size=s.size]) {}
может быть, хотя и как
Stack(const Stackamp; s) : size(), x(new int[size=s.size]) {}
но на самом деле это не так, поскольку на самом деле запись size()
инициализировала бы ее значением, что означает, что она инициализирована нулем, но поскольку инициализацию синтезирует компилятор, происходит инициализация по умолчанию[1], что означает, что она не инициализирована. Затем вы присваиваете ему значение при x
инициализации. Это «безопасно», то есть работает в данном случае, но я бы не рекомендовал это делать.
Stack(const Stackamp; s) : size(s.size), x(new int[s.size]) {}
инициализирует оба элемента, и если вы когда-либо измените их порядок в классе, у вас все равно будет правильное поведение.
Комментарии:
1. Должен любить язык программирования, где для инициализации требуется целый тезис, а? 🙂 Извините за придирки, но это опасная почва, на которой в любом случае работает OP.
2. @StoryTeller Не беспокойтесь. Я бы предпочел выбрать nit, поскольку это долгий путь по кроличьей норе 🙂
Ответ №2:
Члены класса всегда инициализируются в порядке объявления, независимо от порядка, в котором вы указываете их в списке инициализации элементов. И если вы их опускаете, они инициализируются по умолчанию. Итак, делая это:
Stack(const Stackamp; s) : x(new int[size=s.size]) {}
Означает, что size
по умолчанию инициализируется первым. Это оставляет его с неопределенным значением (поскольку предполагается, что основные типы инициализированы по умолчанию). Затем вычисляется инициализатор для x
. Часть вычисления new int[size=s.size]
включает выражение присваивания size=s.size
, которое изменяется size
в качестве побочного эффекта. Итак, ваш код здесь правильный, несмотря на то, что он может вызвать удивление.
Когда вы меняете порядок элементов, присвоение происходит до size
предполагаемой инициализации. Это оставляет ваш код открытым для неопределенного поведения.
Комментарии:
1. Вы уверены, что присвоение должно происходить после инициализации по умолчанию? ( Упорядочивается ли это после ?)
2. @MartinBonner — Я смутно припоминаю, что читал это однажды. Позвольте мне перепроверить.
3. @MartinBonner Это так. здесь есть примечание , и я могу найти актуальное предложение, которое применимо, если вам это действительно нужно.
4. @MartinBonner — Пара timsong-cpp.github.io/cppwp/intro.execution#5.4 с timsong-cpp.github.io/cppwp/intro.execution#9 и учтите, что инициализация выполняется в порядке объявления. Итак, да, теперь я уверен.
Ответ №3:
Из Cppreference.com страница о конструкторах:
Имена, которые отображаются в списке выражений или в списке инициализации в фигурных скобках, вычисляются в области конструктора:
Итак, size
здесь относится к this->size
области видимости конструктора, и присваивание size=s.size
является допустимым выражением.
Само собой разумеется, что вы не должны ожидать, что это пройдет проверку кода 🙂
Ответ №4:
Нет, он переписывает его в это:
class Stack{
public:
int size;
int* x;
Stack() : size(10), x(new int[10]) {}
Stack(const Stackamp; s) :size(), x(new int[size=s.size]) {}
};
size()
был бы конструктором по умолчанию для объекта, size
но int
у s его нет, поэтому это просто пустой оператор и size
остается неинициализированным. Или это так?
Я бы сказал, что этот код может генерировать неопределенное поведение. Переменные-члены инициализируются в том порядке, в котором они объявлены согласно 10.9.2 инициализации баз и элементов. Но порядок вычисления выражений, используемых для инициализации, не определен. Таким образом, size=s.size
может вызываться либо до, либо после size()
. Для типов классов это было бы проблемой, но я не уверен, гарантированно ли size()
отсутствие операции и может ли компилятор решить инициализировать переменную, например, 0.
Комментарии:
1. Смотрите комментарии под ответом рассказчика. Это не неопределенное поведение (но в любом случае очень вонючее).
2. @MartinBonner О, не видел этого при написании моего ответа. Спасибо.