Эта программа имеет четко определенное поведение и демонстрирует ошибку g ++.
Единственная сомнительная часть времени выполнения - во время инструкции (*f)();
. Поведение этой линии можно разделить по частям. Ниже приведены номера стандартных разделов: N3485; извините, если некоторые из них не соответствуют C ++ 11.
*f
- это просто встроенный унарный оператор на необработанном указателе на тип класса. Здесь нет проблем. Единственной другой оценкой является выражение функции-вызова (*f)()
, которое вызывает void std::function<void()>::operator() const
. Тогда это полное выражение является отброшенным значением.
20.8.11.2.4:
R operator()(ArgTypes... args) const
Эффекты:
blockquote>INVOKE
(obj, std::forward<ArgTypes>(args)..., R)
гдеobj
- целевой объект*this
.(Я заменил «
f
» в стандарте на «obj
», чтобы уменьшить путаницу сmain
]f
.)Здесь
obj
является копией лямбда-объекта,ArgTypes
является пустым пакетом параметров из специализацииstd::function<void()>
, аR
-void
.Псевдомакрокоманда
INVOKE
определена в 20.8.2. Поскольку типobj
не является указателем на член,INVOKE
(obj, void)
определяется какobj()
, неявно преобразованный вvoid
.5.1.2p5:
Тип замыкания для lambda-expression имеет общедоступный оператор вызова функции
blockquote>inline
...... с точно описанным объявлением. В этом случае это оказывается
void operator() const
. И его определение точно описано также:5.1.2p7:
Компонентный оператор лямбда-выражения дает функцию-тело оператора вызова функции, но для целей поиска имени, определения типа и значения
blockquote>this
и преобразования id-выражений , относящихся к нестатические члены класса в выражения доступа к членам класса, используя(*this)
, составной оператор рассматривается в контексте лямбда-выражения .5.1.2p14:
Для каждого объекта, захваченного копией, в типе замыкания объявляется неназванный нестатический элемент данных.
blockquote>5.1.2p17:
Каждое id-выражение , которое является нечетным использованием объекта, захваченного копией, преобразуется в доступ к соответствующему неназванному элементу данных из тип закрытия.
blockquote>Таким образом, оператор вызова лямбда-функции должен быть эквивалентен:
void __lambda_type::operator() const { delete __unnamed_member_f; }
(где я изобрел некоторые имена для неназванного лямбда-типа и неназванного элемента данных.)
Единственный оператор этого оператора вызова, конечно, эквивалентен
delete (*this).__unnamed_member_f;
. Итак, мы имеем:
- Разъем размытия
operator*
в унарнойoperator*
)- Выражение доступа к членству
- Вычисление значения (aka lvalue- to-rvalue) для субобъекта-члена
- Сжатие
delete
выражение Invokesstd::function<void()>::~function()
Вызываетvoid operator delete(void*)
И, наконец, в 5.3.5p4:
cast-expression в delete-expression будет оцениваться ровно один раз.
blockquote>( Здесь g ++ ошибается, делая второе вычисление значения в подобъекте элемента между вызовом деструктора и функцией освобождения.)
Этот код не может вызывать каких-либо других вычислений или побочных эффектов после выражения
delete
.Существуют некоторые допуски для поведения, определяемого реализацией в лямбда-типах и lam bda, но ничто не влияет на что-либо выше:
5.1.2p3:
Реализация может определять тип закрытия иначе, чем описано ниже, если это не изменяет наблюдаемое поведение программы, отличное от изменения:
blockquote>
- размера и / или выравнивания типа замыкания,
- , является ли тип замыкания тривиально копируемым,
- является ли тип замыкания стандартным классом макета или
- , является ли тип замыкания классом POD.