L’optimisation des appels en attente et la RAII peuvent-elles coexister?

Je ne peux pas penser à un vrai langage RAII qui comporte également une optimisation des appels dans les spécifications, mais je sais que de nombreuses implémentations C ++ peuvent le faire en tant qu’optimisation spécifique.

Cela pose une question pour les implémentations qui le font: étant donné que les destructeurs sont appelés à la fin de la scope d’une variable automatique et non par une routine distincte de récupération de place, ne viole-t-il pas la contrainte de TCO selon laquelle un appel récursif doit être la dernière instruction du processus? fin d’une fonction?

Par exemple:-

#include  class test_object { public: test_object() { std::cout << "Constructing...\n"; } ~test_object() { std::cout << "Destructing...\n"; } }; void test_function(int count); int main() { test_function(999); } void test_function(int count) { if (!count) return; test_object obj; test_function(count - 1); } 

“Construire …” serait écrit 999 fois, puis “Destructing …” 999 fois. En fin de compte, 999 instances test_object seraient automatiquement allouées avant le déroulement. Mais en supposant qu’une implémentation ait un coût total de possession, existe-t-il 1000 frameworks de stack ou un seul?

Le destructeur après l’appel récursif est-il en conflit avec les exigences d’implémentation du coût total de possession de facto?

    Pris au pied de la lettre, il semblerait que la RAII fonctionne contre le TCO. Cependant, rappelez-vous qu’il existe un certain nombre de façons pour le compilateur de “s’en tirer”, pour ainsi dire.

    Le premier cas, le plus évident, est si le destructeur est sortingvial, c’est-à-dire qu’il est le destructeur par défaut (généré par le compilateur) et que tous les sous-objects ont également des destructeurs sortingviaux. Le destructeur est donc inexistant (toujours optimisé). Dans ce cas, le TCO peut être effectué comme d’habitude.

    Ensuite, le destructeur pourrait être en ligne (son code est pris et placé directement dans la fonction au lieu d’être appelé comme une fonction). Dans ce cas, cela revient simplement à avoir du code de “nettoyage” après l’instruction de retour. Le compilateur est autorisé à réordonner les opérations s’il peut déterminer que le résultat final est identique (la règle “as-if”), et il le fera (en général) si la réorganisation conduit à un meilleur code, et je suppose que le coût total de possession est l’une des considérations appliquées par la plupart des compilateurs (c’est-à-dire que s’il peut réorganiser les choses de telle sorte que le code soit adapté au coût total de possession, il le fera).

    Et pour le rest des cas, où le compilateur ne peut pas être “assez intelligent” pour le faire lui-même, cela devient alors la responsabilité du programmeur. La présence de cet appel de destructeur automatique rend un peu plus difficile pour le programmeur de voir le code de nettoyage inhibant le TCO après l’appel final, mais cela ne fait aucune différence en ce qui concerne la capacité du programmeur à effectuer la vérification. fonctionner un candidat pour TCO. Par exemple:

     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 implémentation naïve de RAII_recursion pourrait être:

     void RAII_recursion(int a) { std::vector arr(a); // do some stuff with vector "arr" RAII_recursion(--a); // tail-call }; // arr gets destroyed here, not good for TCO. 

    Mais un programmeur avisé peut toujours voir que cela ne fonctionnera pas (à moins que le destructeur de vecteur soit en ligne, ce qui est probablement le cas dans ce cas), et puisse facilement remédier à la situation:

     void RAII_recursion(int a) { { std::vector arr(a); // do some stuff with vector "arr" }; // arr gets destroyed here RAII_recursion(--a); // tail-call }; 

    Et je suis à peu près sûr que vous pourrez démontrer qu’il n’existe dans l’ensemble aucun cas où ce type d’astuce ne puisse être utilisée pour garantir l’application du TCO. Ainsi, RAII rend simplement un peu plus difficile de voir si le TCO peut être appliqué. Mais je pense que les programmeurs qui sont assez avisés pour concevoir des appels récursifs capables de gérer le TCO sont également assez avisés pour voir ces appels de destructeurs “cachés” qu’il faudrait forcer avant l’appel final.

    NOTE AJOUTÉE: Regardez de cette façon, le destructeur cache un code de nettoyage automatique. Si vous avez besoin du code de nettoyage (c’est-à-dire, un destructeur non sortingvial), vous en aurez besoin, que vous utilisiez ou non RAII (par exemple, un tableau de style C ou autre). Et puis, si vous voulez que le TCO soit possible, il faut que le nettoyage soit possible avant l’appel final (avec ou sans RAII), et il est possible, alors il est possible de forcer la destruction des objects RAII. avant l’appel final (par exemple, en les plaçant dans un champ supplémentaire).

    Si le compilateur exécute le TCO, l’ordre dans lequel les destructeurs sont appelés est modifié en fonction du moment où il ne le fait pas.

    Si le compilateur peut prouver que cette réorganisation n’a pas d’importance (par exemple, si le destructeur est sortingvial), alors, selon la règle as-if , il peut effectuer un TCO. Cependant, dans votre exemple, le compilateur ne peut pas prouver cela et ne fera pas de TCO.