На первый взгляд может показаться, что RAII работает против TCO. Однако помните, что существует ряд способов, которыми компилятор может, так сказать, «сойти с рук».
Первый и наиболее очевидный случай: если деструктор тривиален, что означает, что это деструктор по умолчанию (сгенерированный компилятором), и все подобъекты также имеют тривиальные деструкторы, тогда деструктор фактически не существует (всегда оптимизирован). В этом случае TCO может выполняться как обычно.
Затем деструктор может быть встроен (его код берется и помещается непосредственно в функцию, а не вызывается как функция). В этом случае все сводится к тому, что после оператора return имеется некоторый «чистящий» код. Компилятору разрешено переупорядочивать операции, если он может определить, что конечный результат тот же (правило «как если бы»), и он будет делать это (в общем случае), если переупорядочивание приводит к лучшему коду, и я бы предположил, что совокупная стоимость владения является одним из соображений, применяемых большинством компиляторов (т. е. если он может переупорядочить вещи таким образом, чтобы код стал пригодным для совокупной стоимости владения, тогда он это сделает).
А в остальных случаях, когда компилятор не может быть "достаточно умным", чтобы сделать это самостоятельно, ответственность за это ложится на программиста. Присутствие этого автоматического вызова деструктора немного затрудняет для программиста просмотр кода очистки, запрещающего совокупную стоимость владения, после хвостового вызова, но это не имеет никакого значения с точки зрения способности программиста выполнять код очистки. кандидат в ТШО. Например:
void nonRAII_recursion(int a) {
int* arr = new int[a];
// do some stuff with array "arr"
delete[] arr;
nonRAII_recursion(--a); // tail-call
};
Теперь наивная реализация RAII_recursion
может быть:
void RAII_recursion(int a) {
std::vector<int> arr(a);
// do some stuff with vector "arr"
RAII_recursion(--a); // tail-call
}; // arr gets destroyed here, not good for TCO.
Но мудрый программист все же видит, что это не сработает (если только деструктор вектора не встроен, что, скорее всего, в данном случае), и может легко исправить ситуацию:
void RAII_recursion(int a) {
{
std::vector<int> arr(a);
// do some stuff with vector "arr"
}; // arr gets destroyed here
RAII_recursion(--a); // tail-call
};
И я почти уверен, что вы могли бы продемонстрировать, что практически нет случаев, когда такого рода приемы нельзя было бы использовать для обеспечения применимости совокупной стоимости владения. Таким образом, RAII просто немного затрудняет определение возможности применения совокупной стоимости владения. Но я думаю, что программисты, которые достаточно мудры, чтобы спроектировать рекурсивные вызовы с возможностью TCO, также достаточно мудры, чтобы увидеть те «скрытые» вызовы деструктора, которые должны быть принудительно выполнены до хвостового вызова.
ДОБАВЛЕННОЕ ПРИМЕЧАНИЕ. Посмотрите на это так: деструктор скрывает некоторый код автоматической очистки. Если вам нужен код очистки (т. е. нетривиальный деструктор), он понадобится вам независимо от того, используете ли вы RAII или нет (например, массив в стиле C или что-то еще). И затем, если вы хотите, чтобы TCO была возможна, должна быть возможность выполнить очистку перед выполнением хвостового вызова (с RAII или без него), и это возможно, тогда можно принудительно уничтожить объекты RAII. перед хвостовым вызовом (например, поместив их в дополнительную область).
person
Mikael Persson
schedule
22.07.2013