web-dev-qa-db-fra.com

Pourquoi faire l'opérateur new private break std :: shared_ptr?

J'implémente un type polymorphe (appelez-le A) que je veux avoir géré exclusivement via std::shared_ptr. Pour permettre l'utilisation de shared_from_this dans le constructeur de classes dérivées, A est alloué à l'aide de new, puis initialise un membre std::shared_ptr à lui-même pour gérer sa durée de vie automatiquement. Pour aider à appliquer cela, j'ai décidé de rendre le operator new spécifique à la classe privé et je prévois d'utiliser une fonction d'assistance create() au lieu de new et make_shared. La conception peut paraître un peu drôle, mais cela a du sens dans le contexte de la bibliothèque d’interface utilisateur sur laquelle je travaille. Voici un exemple reproductible minimal:

struct A {
    A() : m_sharedthis(this) {

    }

    void destroy(){
        m_sharedthis = nullptr;
    }

    std::shared_ptr<A> self() const {
        return m_sharedthis;
    }

private:
    friend std::shared_ptr<A> create();

    std::shared_ptr<A> m_sharedthis;
};

std::shared_ptr<A> create(){
    auto a = new A();
    return a->self();
}

Cela compile et fonctionne bien. Le problème se pose lorsque j'ajoute le code suivant au corps de A:

struct A {
    ...
private:
    void* operator new(size_t size){
        return ::operator new(size);
    }
    void operator delete(void* ptr){
        ::operator delete(ptr);
    }
    ...
};

Maintenant, cela ne parvient pas à compiler sur MSVC 2017 avec un message d'erreur plutôt cryptique:

error C2664: 'std::shared_ptr<A>::shared_ptr(std::shared_ptr<A> &&) noexcept': cannot convert argument 1 from 'A *' to 'std::nullptr_t'
note: nullptr can only be converted to pointer or handle types

Que se passe t-il ici? Pourquoi le constructeur std::shared_ptr essaie-t-il de n'accepter que nullptr soudainement?

EDIT: Oui, dans le code actuel, la classe dérive de std::enable_shared_from_this, mais il n’était pas nécessaire de reproduire l’erreur.

7
alter igel

Lorsque j'ai essayé votre code, j'ai eu une erreur totalement différente:

error C2248: 'A::operator delete': cannot access private member declared in class 'A'

Et le coupable est-ce

m_sharedthis(this)

Vous fournissez un pointeur, mais pas de suppression. Donc, std::shared_ptr essaiera d'utiliser le fichier deleter par défaut, qui contient probablement une expression delete sur votre type. Étant donné que ce fichier n'est pas lié à votre classe, il ne peut pas accéder au operator delete privé.

Une solution de contournement consiste à fournir un suppresseur personnalisé

m_sharedthis(this, [](A* a) {delete a;})

Le lambda, ayant été défini dans l'étendue de la classe, a accès à operator delete au point de sa définition.

Sur une note sans rapport. Votre code tel qu'il est écrit va laisser fuir tous ces objets. Puisque les objets ont tous une forte référence à eux-mêmes, comment vont-ils jamais atteindre un nombre de références égal à zéro? Pensez à utiliser std:enable_shared_from_this à la place.

12
StoryTeller

L'erreur cryptique de visual studio est provoquée par le constructeur shared_ptr qui prend simplement un pointeur désactivé pour les types non supprimables:

template<class _Ux,

    enable_if_t<conjunction_v<conditional_t<is_array_v<_Ty>, _Can_array_delete<_Ux>, _Can_scalar_delete<_Ux>>,

        _SP_convertible<_Ux, _Ty>>, int> = 0>

    explicit shared_ptr(_Ux * _Px)

Le constructeur est désactivé car c ++ 17 déclare:

De plus, ce constructeur ne participe pas à la résolution de surcharge si l'expression de suppression n'est pas bien formée.

Cela laisse les divers constructeurs copier et déplacer en tant que constructeurs à un seul argument, le compilateur choisit le constructeur du déplacement comme correspondance la plus proche mais ne peut pas convertir un pointeur brut en shared_ptr<T>&&, la partie nullptr_t du message d'erreur est due au fait que le compilateur tente utilisez-le comme étape intermédiaire car il est convertible en shared_ptr<T>&&.

Vous devez fournir un suppresseur personnalisé pour résoudre ce problème:

m_sharedthis(this, [](A* a) {delete a;})

Comme indiqué par d'autres, vos objets ne seront jamais supprimés car ils contiennent une forte référence à eux-mêmes. Vous devez changer m_sharedthis en std::weak_ptr ou utiliser std::shared_from_this.

5
Alan Birtles

Vous avez désactivé "n'importe qui sauf la classe A" pour créer et détruire des objets de la classe A. Cela signifie qu'un objet temporel de la classe A ne peut être créé nulle part ailleurs.

Il est probable que la création d'objet temporel vous "a cassé" le modèle SFINAE du modèle.

le constructeur shared_ptr accepte l'allocateur et le suppresseur pouvant encapsuler certains moyens légaux de supprimer votre objet.

0
Swift - Friday Pie