web-dev-qa-db-fra.com

Le deleter d'un shared_ptr est-il stocké en mémoire alloué par l'allocateur personnalisé?

Dis que j'ai un shared_ptr avec un allocateur personnalisé et un suppresseur personnalisé.

Je ne trouve rien dans la norme qui parle de l'endroit où le suppresseur doit être stocké: il ne dit pas que l'allocateur personnalisé sera utilisé pour la mémoire du suppresseur, et il ne dit pas qu'il won ' t être.

Est-ce non spécifié ou manque-t-il simplement quelque chose?

22

util.smartptr.shared.const/9 en C++ 11:

Effets: construit un objet shared_ptr qui possède l'objet p et le deleter d. Les deuxième et quatrième constructeurs doivent utiliser une copie de a pour allouer de la mémoire à un usage interne.

Les deuxième et quatrième constructeurs ont ces prototypes:

template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template<class D, class A> shared_ptr(nullptr_t p, D d, A a);

Dans la dernière version, util.smartptr.shared.const/10 est équivalent à notre objectif:

Effets: construit un objet shared_ptr qui possède l'objet p et le deleter d. Lorsque T n'est pas un type de tableau, les premier et second constructeurs activent shared_from_this avec p. Les deuxième et quatrième constructeurs doivent utiliser une copie de a pour allouer de la mémoire à un usage interne. Si une exception est levée, d(p) est appelé.

L'allocateur est donc utilisé s'il est nécessaire de l'allouer dans la mémoire allouée. Sur la base de la norme actuelle et des rapports de défauts pertinents, l'allocation n'est pas obligatoire mais assumée par le comité.

  • Bien que l'interface de shared_ptr Permette une implémentation où il n'y a jamais de bloc de contrôle et où tous shared_ptr Et weak_ptr Sont placés dans une liste chaînée, il n'y a pas une telle implémentation en pratique. De plus, la formulation a été modifiée en supposant, par exemple, que le use_count Est partagé.

  • Le deleter est nécessaire pour déplacer uniquement constructible. Ainsi, il n'est pas possible d'avoir plusieurs copies dans le shared_ptr.

On peut imaginer une implémentation qui place le deleter dans un shared_ptr Spécialement conçu et le déplace quand le shared_ptr Spécial est supprimé. Bien que l'implémentation semble conforme, elle est également étrange, d'autant plus qu'un bloc de contrôle peut être nécessaire pour le compte d'utilisation (il est peut-être possible mais encore plus étrange de faire la même chose avec le compte d'utilisation).

DR pertinents que j'ai trouvés: 545 , 575 , 2434 (qui reconnaissent que toutes les implémentations utilisent un bloc de contrôle et semblent impliquer que le multithreading les contraintes l'imposent quelque peu), 2802 (qui exige que le déléteur ne se déplace que constructible et empêche ainsi l'implémentation où le déléteur est copié entre plusieurs shared_ptr).

11
AProgrammer

De std :: shared_ptr nous avons:

Le bloc de contrôle est un objet alloué dynamiquement qui contient:

  • soit un pointeur vers l'objet géré ou l'objet géré lui-même;
  • le deleter (effacé);
  • l'allocateur (effacé);
  • le nombre de shared_ptrs qui possèdent l'objet géré;
  • le nombre de faibles_ptr qui se réfèrent à l'objet géré.

Et de std :: allocate_shared nous obtenons:

template< class T, class Alloc, class... Args >
shared_ptr<T> allocate_shared( const Alloc& alloc, Args&&... args );

Construit un objet de type T et l'enveloppe dans un std :: shared_ptr [...] afin d'utiliser une allocation pour le bloc de contrôle de le pointeur partagé et l'objet T.

Il semble donc que std :: allocate_shared devrait allouer le deleter avec votre Alloc.

EDIT: Et de n4810 §20.11.3.6 Création [util.smartptr.shared.create]

1 Les exigences communes qui s'appliquent à tous make_shared, allocate_shared, make_shared_default_init, et allocate_shared_default_init les surcharges, sauf indication contraire, sont décrites ci-dessous.

[...]

7 Remarques: (7.1) - Les implémentations ne doivent pas effectuer plus d'une allocation de mémoire. [Remarque: Cela fournit une efficacité équivalente à un pointeur intelligent intrusif. —Fin note]

[Souligne tout le mien]

Donc, la norme dit que std::allocate_shared devrait utilisez Alloc pour le bloc de contrôle.

4
Paul Evans

Je crois que cela n'est pas spécifié.

Voici la spécification des constructeurs concernés: [util.smartptr.shared.const]/1

template<class Y, class D> shared_ptr(Y* p, D d);
template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template <class D> shared_ptr(nullptr_t p, D d);
template <class D, class A> shared_ptr(nullptr_t p, D d, A a);

Effets: Construit un objet shared_­ptr Qui possède l'objet p et le suppresseur d. Lorsque T n'est pas un type de tableau, les premier et second constructeurs activent shared_­from_­this Avec p. Les deuxième et quatrième constructeurs doivent utiliser une copie de a pour allouer de la mémoire à un usage interne . Si une exception est levée, d(p) est appelée.

Maintenant, mon interprétation est que lorsque l'implémentation a besoin de mémoire pour un usage interne, elle le fait en utilisant a. Cela ne signifie pas que l'implémentation doit utiliser cette mémoire pour tout placer. Par exemple, supposons qu'il y ait cette implémentation bizarre:

template <typename T>
class shared_ptr : /* ... */ {
    // ...
    std::aligned_storage<16> _Small_deleter;
    // ...
public:
    // ...
    template <class _D, class _A>
    shared_ptr(nullptr_t, _D __d, _A __a) // for example
        : _Allocator_base{__a}
    {
        if constexpr (sizeof(_D) <= 16)
            _Construct_at(&_Small_deleter, std::move(__d));
        else
            // use 'a' to allocate storage for the deleter
    }
// ...
};

Cette implémentation "utilise-t-elle une copie de a pour allouer de la mémoire à un usage interne"? Oui. Il n'alloue jamais de mémoire sauf en utilisant a. Il y a beaucoup de problèmes avec cette implémentation naïve, mais disons qu'elle passe à l'utilisation d'allocateurs dans tous les cas sauf le plus simple dans lequel le shared_ptr Est construit directement à partir d'un pointeur et n'est jamais copié ou déplacé ou autrement référencé et là n'y a pas d'autres complications. Le fait est que le fait de ne pas imaginer qu'une implémentation valide ne prouve pas en soi qu'elle ne peut pas théoriquement exister. Je ne dis pas qu'une telle implémentation peut réellement être trouvée dans le monde réel, juste que la norme ne semble pas l'interdire activement.

3
L. F.