TCOに対してはRAIのように見えるでしょう。しかし、コンパイラが "それを取り除く"ことができる方法は数多くあることに留意してください。
デストラクタが簡単で、デフォルトのデストラクタ(コンパイラ生成)であり、すべてのサブオブジェクトが些細なデストラクタでもある場合、デストラクタは事実上存在しません(常に最適化された)。その場合、通常通りにTCOを実行することができます。
次に、デストラクタをインライン化することができます(コードは関数のように呼び出されるのではなく、関数に直接渡されます)。その場合、return文の後に "クリーンアップ"コードがあるだけです。コンパイラは、最終結果が同じであるかどうかを判断できる場合(「if-if」ルール)、再順序付けによってより良いコードが得られる場合(一般的にはそうする)、操作を並べ替えることができます。 TCOは、ほとんどのコンパイラで適用されている考慮事項の1つであると想定します(つまり、コードがTCOに適したものになるように並べ替えることができれば、それが実行されます)。
コンパイラがそれ自身でそれを行うのに十分にスマートにできない場合の残りの場合は、プログラマの責任となります。この自動デストラクタ呼び出しが存在するため、テールコール後にTCOを禁止するクリーンアップコードをプログラマが見るのが少し難しくなりますが、プログラマがテールコールを作成する能力の点で違いはありませんTCOの候補として機能します。たとえば:
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 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"
}; // arr gets destroyed here
RAII_recursion(--a); // tail-call
};
をそして私はあなたがこの種ののない場合は、基本的に存在しないことを示している可能性がかなり確信していますトリックを使用してTCOを確実に適用することはできませんでした。したがって、RAIIは、TCOを適用できるかどうかを確認するのが少し難しくなります。しかし、私は、TCO対応再帰呼び出しを設計するのに十分な賢明なプログラマーは、テールコールの前に強制的に実行する必要のある「隠された」デストラクタ呼び出しを見るのに十分な立場にあると考えています。
注:このように見て、デストラクタは自動クリーンアップコードを隠します。クリーンアップコード(つまり、非自明なデストラクタ)が必要な場合は、RAIIを使用するかどうかにかかわらず(たとえばCスタイルの配列など)、必要になります。そして、TCOを可能にしたい場合は、テールコール(RAIIの有無にかかわらず)を行う前にクリーンアップを行うことが可能でなければならず、RAIIオブジェクトを強制的に破棄することは可能ですテールコールの前に(例えば、それらを余分なスコープの中に入れて)。
実際に面白いですが。私は「もちろんTCOを防ぐことはできません」と言うつもりだったが、もっと見ると、もっとそうだと思う。 –
ここでテールコールはありません。そのままコンパイルしてください)。理論的には良いコンパイラがパターンに気づき、これを2つのループに変えることができます。 –
短い答え:はい、確定的なデストラクタはTCOを防ぎます。長い答え:実際にはいくつかのコンパイラ(私が信じるLLVMなど)はTCOのより許容された形式を実装し、より多くのケースを許容するかもしれません。 –