Является ли замена "этого" на другой тип допустимым?

В комментариях и ответах на этот вопрос: оптимизация компилятора виртуальных функций c++ утверждается, что вызов виртуальной функции в цикле не может быть девиртуализирован, так как виртуальная функция может заменить this другим объектом с помощью размещения new, например:

void A::foo() { // virtual 
   static_assert(sizeof(A) == sizeof(Derived)); 
   new(this) Derived; 
}

Пример из статьи блога LLVM о девиртуализации

Теперь мой вопрос: это разрешено стандартом?

Я мог бы найти это на cppreference о повторном использовании хранилища: (акцент мой)

Программа не требует вызова деструктора объекта, чтобы завершить его время жизни, если объект тривиально разрушен или если программа не полагается на побочные эффекты деструктора. Однако, если программа завершает время жизни нетривиального объекта, она должна гарантировать, что новый объект того же типа будет создан на месте (например, через новое место размещения), прежде чем деструктор можно будет назвать неявным

Если новый объект должен иметь один и тот же тип, он должен иметь одинаковые виртуальные функции. Таким образом, невозможно иметь другую виртуальную функцию, и, таким образом, девиртуализация приемлема.

Или я что-то неправильно понимаю?

Ответы

Ответ 1

Похоже, что вы намерены использовать новый объект, используя дескрипторы (указатели, ссылки или исходное имя переменной), существовавшие до его отдыха. Это разрешено только в том случае, если тип экземпляра не изменен, а также некоторые другие условия, исключая объекты const и под-объекты:

Из [basic.life]:

Если после того, как срок жизни объекта закончился и перед хранилищем, который объект занят, повторно используется или выпущен, создается новый объект в месте хранения, в котором находился исходный объект, указатель, указывающий на исходный объект, ссылку, которая ссылается на исходный объект, или имя исходного объекта будет автоматически ссылаться на новый объект и, как только время жизни нового объекта будет запущено, можно использовать для управления новым объектом, если:

  • хранилище для нового объекта точно накладывает место хранения, в котором находился исходный объект,

    а также

  • новый объект имеет тот же тип, что и исходный объект (игнорируя cv-квалификаторы верхнего уровня) и

  • тип исходного объекта не является константным и, если тип класса, не содержит нестатического члена данных, тип которого является const-квалифицированным или ссылочным, и

  • исходный объект был наиболее производным объектом типа T а новый объект является наиболее производным объектом типа T (т.е. они не являются подобъектами базового класса).

Ваша цитата из Стандарта является лишь следствием этого.

Ваш предложенный "контрпримерный пример" не отвечает этим требованиям, поэтому все попытки доступа к объекту после его замены вызывают неопределенное поведение.

Сообщение в блоге даже указывало на это в самом следующем предложении после кода примера, на который вы смотрели.

Ответ 2

В приведенной вами цитате написано:

Если программа завершает время жизни нетривиального объекта, она должна гарантировать, что новый объект того же типа будет создан на месте (например, через размещение нового), прежде чем деструктор можно будет назвать неявным

Цель этого утверждения связана с чем-то, немного отличающимся от того, что вы делаете. Утверждение предназначено для того, чтобы сказать, что когда вы уничтожаете объект, не уничтожая его имя, что-то все еще относится к этому хранилищу с исходным типом, o вам нужно построить там новый объект, чтобы при возникновении неявного разрушения существовал действительный объект разрушать. Это актуально, например, если у вас есть автоматическая ("стек") переменная, и вы вызываете ее деструктор - вам нужно создать новый экземпляр там до того, как вызывается деструктор, когда переменная выходит за пределы области видимости.

Утверждение в целом и предложение "того же типа", в частности, не имеют отношения к обсуждаемой теме, а именно: разрешено ли вам создавать другой полиморфный тип, имеющий те же требования к хранилищу вместо Старый. Я не знаю, почему вы не должны этого делать.

Теперь, когда вы говорите, вопрос, с которым вы связаны, делает что-то другое: он вызывает функцию, использующую неявное this в цикле, и возникает вопрос, может ли компилятор предположить, что vptr для this не изменится в этом цикле. Я считаю, что компилятор мог (и clang -fstrict-vtable-pointers) предположить это, потому что this допустимо только в том случае, если тип был таким же после размещения new.

Поэтому, пока кавычки из предоставленного вами стандарта не имеют отношения к этой проблеме, конечным результатом является то, что оптимизатор может девиртуализировать вызовы функций, выполненные в цикле, в предположении, что тип *this (или его vptr ) невозможно изменить. Тип объекта, хранящегося по адресу (и его vptr), может измениться, но если это так, то старый this уже недействителен.