Pourquoi ai-je besoin de std :: get_temporary_buffer?

84

Dans quel but dois-je utiliser std::get_temporary_buffer? Standard dit ce qui suit:

Obtient un pointeur vers le stockage suffisant pour stocker jusqu'à n objets T adjacents.

Je pensais que le tampon serait alloué sur la pile, mais ce n'est pas vrai. Selon la norme C ++, ce tampon n'est en fait pas temporaire. Quels avantages cette fonction a-t-elle sur la fonction globale ::operator new, qui ne construit pas non plus les objets. Ai-je raison de dire que les affirmations suivantes sont équivalentes?

int* x;
x = std::get_temporary_buffer<int>( 10 ).first;
x = static_cast<int*>( ::operator new( 10*sizeof(int) ) );

Cette fonction n'existe-t-elle que pour le sucre syntaxique? Pourquoi y a-t-il temporarydans son nom?


Un cas d'utilisation a été suggéré dans le Dr. Dobb's Journal du 1er juillet 1996 pour l'implémentation d'algorithmes:

Si aucun tampon ne peut être alloué, ou s'il est plus petit que demandé, l'algorithme fonctionne toujours correctement, il ralentit simplement.

Kirill V. Lyadvinsky
la source
2
FYI, std::get_temporary_buffersera obsolète en C ++ 17.
Deqing le
1
@Deqing Oui. Il sera également supprimé dans C ++ 20 et pour une bonne raison (comme ceux mentionnés ci-dessous). Alors déplacez-vous le spectateur ..
Nikos

Réponses:

44

Stroustrup dit dans "Le langage de programmation C ++" ( §19.4.4 , SE):

L'idée est qu'un système peut garder un certain nombre de tampons de taille fixe prêts pour une allocation rapide de sorte que la demande d'espace pour n objets peut générer de l'espace pour plus de n . Cependant, cela peut aussi rapporter moins, donc une façon d'utiliser get_temporary_buffer()est de demander beaucoup avec optimisme, puis d'utiliser ce qui se trouve être disponible.
[...] Parce qu'il get_temporary_buffer()est de bas niveau et qu'il est susceptible d'être optimisé pour la gestion des tampons temporaires, il ne doit pas être utilisé comme alternative à new ou allocator :: allocate () pour obtenir un stockage à plus long terme.

Il commence également l'introduction des deux fonctions avec:

Les algorithmes nécessitent souvent un espace temporaire pour fonctionner de manière acceptable.

... mais ne semble pas fournir de définition de temporaire ou à plus long terme .

Une anecdote dans "From Mathematics to Generic Programming" mentionne que Stepanov a fourni une implémentation fictive d'espace réservé dans la conception originale de la STL:

À sa grande surprise, il a découvert des années plus tard que tous les principaux fournisseurs qui fournissent des implémentations STL utilisent toujours cette terrible implémentation [...]

Georg Fritzsche
la source
12
Il semble que l'implémentation de ceci par VC ++ est simplement un appel de boucle operator newavec des arguments successivement plus petits jusqu'à ce que l'allocation réussisse. Aucune optimisation spéciale là-bas.
jalf
9
Idem avec g ++ 4.5 - semble bien intentionné mais ignoré par les vendeurs.
Georg Fritzsche
4
On dirait qu'ils auraient dû envelopper cette fonctionnalité dans une classe appeléecrazy_allocator
0xbadf00d
Mais restons sérieux - Peut-être que l'on serait heureux d'allouer beaucoup de stockage. Ce que nous pouvons maintenant faire, c'est demander au système d'obtenir cette énorme quantité de stockage en utilisant get_temporary_buffer. Cependant, si nous recevons moins que le montant demandé (ce qui serait vraiment dommage), nous continuons d'essayer de faire notre travail avec le stockage que nous avons. Peut-être mieux que d'attraper les bad_allocexceptions causées par la tentative d'allouer plus de mémoire que disponible. Cependant, l'utilité réelle se maintient et tombe avec une bonne mise en œuvre.
0xbadf00d
@jalf Il est obsolète en C ++ 17 :) (selon cppreference.com ).
4LegsDrivenCat
17

Le gars de la bibliothèque standard de Microsoft dit ce qui suit ( ici ):

  • Pouvez-vous expliquer quand utiliser "get_temporary_buffer"

Il a un objectif très spécialisé. Notez qu'il ne lance pas d'exceptions, comme new (nothrow), mais il ne construit pas non plus d'objets, contrairement à new (nothrow).

Il est utilisé en interne par la STL dans des algorithmes comme stable_partition (). Cela se produit quand il y a des mots magiques comme N3126 25.3.13 [alg.partitions] / 11: stable_partition () a une complexité "Au plus (dernier - premier) * log (dernier - premier) swaps, mais seulement un nombre linéaire de swaps s'il y a est assez de mémoire supplémentaire. " Lorsque les mots magiques "s'il y a suffisamment de mémoire supplémentaire" apparaissent, la STL utilise get_temporary_buffer () pour tenter d'acquérir de l'espace de travail. S'il le peut, il peut implémenter l'algorithme plus efficacement. Si ce n'est pas le cas, parce que le système fonctionne dangereusement près de la mémoire insuffisante (ou que les plages impliquées sont énormes), l'algorithme peut revenir à une technique plus lente.

99,9% des utilisateurs de STL n'auront jamais besoin de connaître get_temporary_buffer ().

Jérémie
la source
9

La norme dit qu'elle alloue du stockage pour un maximum d' n éléments. En d'autres termes, votre exemple peut renvoyer une mémoire tampon suffisamment grande pour 5 objets seulement.

Il semble cependant assez difficile d'imaginer un bon cas d'utilisation pour cela. Peut-être que si vous travaillez sur une plate-forme très limitée en mémoire, c'est un moyen pratique d'obtenir «autant de mémoire que possible».

Mais sur une plate-forme aussi contrainte, j'imagine que vous contourneriez le plus possible l'allocateur de mémoire et utiliseriez un pool de mémoire ou quelque chose sur lequel vous avez un contrôle total.

jalf
la source
4

Dans quel but je devrais utiliser std::get_temporary_buffer?

La fonction est obsolète dans C ++ 17, donc la bonne réponse est maintenant "sans but, ne l'utilisez pas".

Raedwald
la source
2
ptrdiff_t            request = 12
pair<int*,ptrdiff_t> p       = get_temporary_buffer<int>(request);
int*                 base    = p.first;
ptrdiff_t            respond = p.sencond;
assert( is_valid( base, base + respond ) );

la réponse peut être inférieure à la demande .

size_t require = 12;
int*   base    = static_cast<int*>( ::operator new( require*sizeof(int) ) );
assert( is_valid( base, base + require ) );

la taille réelle de la base doit être supérieure ou égale à exiger .

PosséderWaterloo
la source
2

Peut-être (juste une supposition) que cela a quelque chose à voir avec la fragmentation de la mémoire. Si vous continuez à allouer et à désallouer beaucoup de mémoire temporelle, mais à chaque fois que vous le faites, vous allouez de la mémoire prévue à long terme après avoir alloué la température, mais avant de la désallouer, vous pouvez vous retrouver avec un tas fragmenté (je suppose).

Ainsi, le get_temporary_buffer pourrait être destiné à être un morceau de mémoire plus gros que ce dont vous auriez besoin qui est alloué une fois (peut-être qu'il y a de nombreux morceaux prêts à accepter plusieurs demandes), et chaque fois que vous avez besoin de mémoire, vous obtenez juste l'un des morceaux. Ainsi, la mémoire ne se fragmente pas.

Daniel Munoz
la source
1
Pensée très intéressante. Bien que cela semble être actuellement implémenté comme un moyen de faire simplement quelque chose qui fonctionne par la plupart des implémentations, cela pourrait très bien être plus étroitement soutenu et intégré au reste des routines de gestion de la mémoire. Je vote pour nous en m'attendant réellement à ce que cela lui convienne, et les commentaires de Bjarnes semblent l'indiquer aussi.
gustaf r
J'ai cherché maintenant ce que Bjarne en dit et il dit qu'il est conçu pour une allocation rapide sans initialisation. Donc, ce serait comme un opérateur void * new (size_t size) réservé à l'allocateur (non initialiseur), mais c'est plus rapide à allouer, car il est préalloué.
Daniel Munoz