Вопрос ИСКЛЮЧИТЕЛЬНО юридический, с точки зрения стандарта языка.
Мнения про недопустимость такого стиля и предложения заменить на delegating constructors не интересуют.
Так вот. Мне встречалось утверждение что вызов другого конструктора изнутри данного:
Klass::Klass(some_params)
{
new (this) Klass(other_params);
}
Здравствуйте, σ, Вы писали:
σ>Вопрос ИСКЛЮЧИТЕЛЬНО юридический, с точки зрения стандарта языка. σ>Это так или нет? И почему?
Не знаю что там в стандарте, но по логике вещей такая конструкция допустима, и, думаю, вполне может себе использоваться
в С++ до 11 стандарта.
Здравствуйте, GhostCoders, Вы писали:
GC>Здравствуйте, σ, Вы писали:
σ>>Вопрос ИСКЛЮЧИТЕЛЬНО юридический, с точки зрения стандарта языка. σ>>Это так или нет? И почему? GC> Не знаю что там в стандарте, но по логике вещей такая конструкция допустима, и, думаю, вполне может себе использоваться GC>в С++ до 11 стандарта.
Здравствуйте!
Тоже с точки кодонаписания — тоже проблем не вижу.
Но, конструктор — генерит asm для VTBL + конструкторы для полей
Т.е. у тебя
Klass::Klass(some_params)
{
// Тута компилер сгенерит код
new (this) Klass(other_params); // И тута компилер сгенерит код
}
Хотя они (компилеры) умные сейчас — мот и не будет...
Достаточно одному из членов класса или предку захватывать какой-либо ресурс в конструкторе -- и получим повторный захват (утечку / дидлок) в placement new без освобождения уже захваченного.
Статья 273 УК
σ>Мне встречалось утверждение что вызов другого конструктора изнутри данного: σ>
σ>Klass::Klass(some_params)
σ>{
σ> new (this) Klass(other_params);
σ>}
σ>
σ>приводит к UB.
σ>Это так или нет? И почему?
Похоже на неявное undefined behavior — это когда в стандарте попросту нет описания того, как некая конструкция должна работать. Насколько я вижу, формально объект, создаваемый конструктором Klass(some_params), и объект, создаваемый выражением new (this) Klass(other_params), — это два разных объекта, и какой там винегрет получится при попытке перезаписать чем-то левым память первого объекта в процессе его создания, непонятно. Закончить время жизни первого объекта создание второго не может, т.к. время жизни первого ещё не началось (из тела конструктора ещё не вышли). В общем, что мы имеем:
1. Допустимость делать reuse памяти для объекта under construction и/или последствия такого действия, вроде, нигде не прописаны.
2. В каком состоянии будет пребывать программа в момент, когда должно стартануть время жизни первого объекта (чью память мы вероломно перезаписали каким-то другим объектом, который не подпадает под специальный случай для subobjects), тоже нигде не прописано.
3. Гарантий, что над этим винегретом из двух объектов в дальнейшем допустимо делать какие-то осмысленные операции, у нас опять же нет.
Тут, пожалуй, можно подумать над тем, когда именно ожидать возникновение undefined behavior: в момент входа управления в конструктор второго объекта, в момент выхода управления из конструктора первого объекта или же в момент первой попытки сделать с их сочетанием что-нибудь такое, что требует наличие валидного объекта. Пожалуй, я бы поставил на первый вариант — возникновение undefined behavior прямо на входе управления в конструктор второго объекта (из соображений п.1 выше).
Здравствуйте, σ, Вы писали:
σ>приводит к UB.
Конкретно этот пример привёдт только к проблемама уровня RAII (особенно если там иерархия), но про это сказали. σ>Это так или нет?
Если понимать как работает, то нет. Я реализовывал даже стейт машину используя этот подход, правда в производных классах нельзя при этом добавлять новые данные или виртуальные методы чтобы не сломать память.
Здравствуйте, σ, Вы писали:
σ>Так вот. Мне встречалось утверждение что вызов другого конструктора изнутри данного: σ>
σ>Klass::Klass(some_params)
σ>{
σ> new (this) Klass(other_params);
σ>}
σ>
σ>приводит к UB. σ>Это так или нет? И почему?
В принципе, N.I. все объяснил, но я хочу слегка дополнить. Если бы речь шла не о конструкторе, а об обычной нестатической функции-члене, при условии, что класс Klass имеет тривиальный деструктор, такой прием был бы легален. В 6.8/6 (Object lifetime) есть даже в примере нечто похожее:
struct B {
virtual void f();
void mutate();
virtual ~B();
};
struct D1 : B { void f(); };
struct D2 : B { void f(); };
void B::mutate() {
new (this) D2; // reuses storage — ends the lifetime of *this
f(); // undefined behavior
... = this; // OK, this points to valid memory
}
Когда же нечто подобное происходит в конструкторе, это совсем другое дело — формально, время жизни объекта начинается только после окончания конструирования. И о том, как должна повести себя программа в стандарте ничего не сказано.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, σ, Вы писали:
σ>Вопрос ИСКЛЮЧИТЕЛЬНО юридический, с точки зрения стандарта языка. σ>Мнения про недопустимость такого стиля и предложения заменить на delegating constructors не интересуют.
σ>Так вот. Мне встречалось утверждение что вызов другого конструктора изнутри данного: σ>
σ>Klass::Klass(some_params)
σ>{
σ> new (this) Klass(other_params);
σ>}
σ>
σ>приводит к UB.
σ>Это так или нет? И почему?
Я бы посоветовал, чтобы не ломалось, сделать вот так:
N. I.:
NI>σ:
σ>>Вопрос ИСКЛЮЧИТЕЛЬНО юридический
NI>Статья 273 УК
σ>>Мне встречалось утверждение что вызов другого конструктора изнутри данного: σ>>
σ>>Klass::Klass(some_params)
σ>>{
σ>> new (this) Klass(other_params);
σ>>}
σ>>
σ>>приводит к UB.
σ>>Это так или нет? И почему?
NI>Похоже на неявное undefined behavior — это когда в стандарте попросту нет описания того, как некая конструкция должна работать.
Я хотел поспорить и сказать, что поведение определено в http://eel.is/c++draft/basic.life#6 и this можно использовать как указатель на сторадж, но понял, что, оказывается, я проглядел "For an object under construction or destruction, see [class.cdtor]". Т.е. объект может быть не только в двух состояниях: когда лайфтайм ещё не начался (уже закончился) или уже начался (и ещё не закончился), но ещё и в "промежуточном" — under construction/destruction.
В общем, вопроса почему UB больше нет — я просто невнимательно читал параграф стандарта.
Здравствуйте, σ, Вы писали:
σ>Вопрос ИСКЛЮЧИТЕЛЬНО юридический, с точки зрения стандарта языка. σ>Мнения про недопустимость такого стиля и предложения заменить на delegating constructors не интересуют.
σ>Так вот. Мне встречалось утверждение что вызов другого конструктора изнутри данного: σ>
σ>Klass::Klass(some_params)
σ>{
σ> new (this) Klass(other_params);
σ>}
σ>
σ>приводит к UB.
Это UB.
Для каждого объекта, для которого вызван конструктор, должен быть вызван деструктор.
Для объектов, управляемых через placement new / delete деструкторы должны быть вызваны
руками. тут этого нет. Конструктор Klass уже сработал, а деструктор его не вызывается.
Объект, конструируемый по placement new также должен создаваться на базе памяти, в которой
не существует валидного (т.е. не прошедшего через деструктор) объекта. Тут это тоже не
соблюдается.
Здравствуйте, MasterZiv, Вы писали:
MZ>Для каждого объекта, для которого вызван конструктор, должен быть вызван деструктор.
Такого правила нет.
Можно переиспользовать память под объектом не вызывая его деструктора http://eel.is/c++draft/basic.life#5:
A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor.
For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released;
(Это, конечно, не позволяет placement new в конструкторе).