Comment appeler :: std :: make_shared sur une classe avec uniquement des constructeurs protégés ou privés?

187

J'ai ce code qui ne fonctionne pas, mais je pense que l'intention est claire:

testmakeshared.cpp

#include <memory>

class A {
 public:
   static ::std::shared_ptr<A> create() {
      return ::std::make_shared<A>();
   }

 protected:
   A() {}
   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

::std::shared_ptr<A> foo()
{
   return A::create();
}

Mais j'obtiens cette erreur lorsque je le compile:

g++ -std=c++0x -march=native -mtune=native -O3 -Wall testmakeshared.cpp
In file included from /usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:52:0,
                 from /usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/memory:86,
                 from testmakeshared.cpp:1:
testmakeshared.cpp: In constructor std::_Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp>::_Sp_counted_ptr_inplace(_Alloc) [with _Tp = A, _Alloc = std::allocator<A>, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’:
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:518:8:   instantiated from std::__shared_count<_Lp>::__shared_count(std::_Sp_make_shared_tag, _Tp*, const _Alloc&, _Args&& ...) [with _Tp = A, _Alloc = std::allocator<A>, _Args = {}, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:986:35:   instantiated from std::__shared_ptr<_Tp, _Lp>::__shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator<A>, _Args = {}, _Tp = A, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:313:64:   instantiated from std::shared_ptr<_Tp>::shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator<A>, _Args = {}, _Tp = A]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:531:39:   instantiated from std::shared_ptr<_Tp> std::allocate_shared(const _Alloc&, _Args&& ...) [with _Tp = A, _Alloc = std::allocator<A>, _Args = {}]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:547:42:   instantiated from std::shared_ptr<_Tp1> std::make_shared(_Args&& ...) [with _Tp = A, _Args = {}]’
testmakeshared.cpp:6:40:   instantiated from here
testmakeshared.cpp:10:8: error: A::A()’ is protected
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:400:2: error: within this context

Compilation exited abnormally with code 1 at Tue Nov 15 07:32:58

Ce message dit essentiellement qu'une méthode aléatoire dans la pile d'instanciation de modèle de ::std::make_sharedne peut pas accéder au constructeur car il est protégé.

Mais je veux vraiment utiliser les deux ::std::make_sharedet empêcher quiconque de créer un objet de cette classe qui n'est pas pointé par un ::std::shared_ptr. Y a-t-il un moyen d'accomplir cela?

Très varié
la source
Vous pouvez marquer en profondeur la fonction qui a besoin du constructeur comme amie, mais ce ne sera pas portable.
Dani
@Dani: Ouais, ce serait bien d'avoir une solution portable. Mais cela fonctionnerait.
Omnifarious

Réponses:

109

Cette réponse est probablement meilleure, et celle que j'accepterai probablement. Mais j'ai également proposé une méthode plus moche, mais qui laisse tout de même être en ligne et ne nécessite pas de classe dérivée:

#include <memory>
#include <string>

class A {
 protected:
   struct this_is_private;

 public:
   explicit A(const this_is_private &) {}
   A(const this_is_private &, ::std::string, int) {}

   template <typename... T>
   static ::std::shared_ptr<A> create(T &&...args) {
      return ::std::make_shared<A>(this_is_private{0},
                                   ::std::forward<T>(args)...);
   }

 protected:
   struct this_is_private {
       explicit this_is_private(int) {}
   };

   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

::std::shared_ptr<A> foo()
{
   return A::create();
}

::std::shared_ptr<A> bar()
{
   return A::create("George", 5);
}

::std::shared_ptr<A> errors()
{
   ::std::shared_ptr<A> retval;

   // Each of these assignments to retval properly generates errors.
   retval = A::create("George");
   retval = new A(A::this_is_private{0});
   return ::std::move(retval);
}

Edit 2017-01-06: J'ai changé cela pour indiquer clairement que cette idée est clairement et simplement extensible aux constructeurs qui prennent des arguments parce que d'autres personnes fournissaient des réponses dans ce sens et semblaient confuses à ce sujet.

Très varié
la source
14
En fait, je suis un grand fan de ces structures sans signification utilisées uniquement comme clés . Je préfère cela à la solution de Luc, mais cela pourrait être mon biais contre l'héritage.
Matthieu M.
2
D'accord, j'aime mieux ça aussi.
ildjarn
3
@Berkus: Alors fais-le protectedau lieu de private. Et par «ça», je fais référence à la this_is_privateclasse, qui devrait peut-être être renommée dans ce cas. Je l'appelle généralement constructor_accessdans mon code.
dalle
1
Malheureusement, cela ne fonctionne pas si votre constructeur prend de vrais paramètres; dans ce cas, vous pouvez simplement passer {}pour la balise privée sans avoir accès au nom du type (testé avec g ++ 4.9.0). Sans paramètres réels, il essaie de construire à Apartir de {}, bien que je ne sache pas pourquoi, et échoue. Je pense que rendre le constructeur this_is_private privé et fournir une méthode statique pour le créer le corrige, car il ne devrait y avoir aucun moyen d'accéder à cette méthode de l'extérieur à moins que vous ne fuyiez le type dans une signature de fonction membre.
Stefan
3
Stefan, si vous donnez this_is_privateun ctor privé, vous pouvez faire de la classe A un ami. Semble fermer la brèche.
Steven Kramer
78

En regardant les exigences de la std::make_sharedcréation de shared_ptr 20.7.2.2.6 [util.smartptr.shared.create], paragraphe 1:

Requiert: L'expression ::new (pv) T(std::forward<Args>(args)...), où pva un type void*et des points de stockage appropriés pour contenir un objet de type T, doit être bien formée. Adoit être un allocateur (17.6.3.5). Le constructeur de copie et le destructeur de Ane doivent pas lancer d'exceptions.

Étant donné que l'exigence est spécifiée de manière inconditionnelle en fonction de cette expression et que des choses comme la portée ne sont pas prises en compte, je pense que des astuces comme l'amitié sont tout à fait possibles.

Une solution simple consiste à dériver de A. Cela n'a pas besoin de créer Aune interface ou même un type polymorphe.

// interface in header
std::shared_ptr<A> make_a();

// implementation in source
namespace {

struct concrete_A: public A {};

} // namespace

std::shared_ptr<A>
make_a()
{
    return std::make_shared<concrete_A>();
}
Luc Danton
la source
1
Oh, c'est une réponse très intelligente, et peut-être meilleure qu'une autre à laquelle j'avais pensé.
Omnifariable
Une question cependant, le shared_ptr ne supprimera-t-il pas un A et non un concret_A, et cela ne pourrait-il pas poser de problèmes?
Omnifarious
8
Ahh, c'est parce que shared_ptrstocke un deleter au moment de l'instanciation, et si vous utilisez make_sharedle deleter doit absolument utiliser le bon type.
Omnifarious
1
@LucDanton Question ne concerne pas les interfaces, comme le titre l'indique, il demande également un cteur privé. De toute façon, c'est pourquoi je suis sur cette question. Un vieux code ayant des classes machiavelli, qui a un ctor privé et une méthode de création retournant un pointeur brut, et j'essaye de les convertir en pointeurs intelligents.
zahir
2
J'aime cette approche (en l'utilisant moi-même) mais vous avez besoin d'un destructeur virtuel. Il s'étend bien aux constructeurs avec des arguments (fournissez simplement un constructeur passthrough). Et si vous utilisez protégé plutôt que privé, vous pouvez le rendre complètement invisible pour les utilisateurs de l'en-tête.
Joe Steele
69

Peut-être la solution la plus simple. Basé sur la réponse précédente de Mohit Aron et incorporant la suggestion de dlf.

#include <memory>

class A
{
public:
    static std::shared_ptr<A> create()
    {
        struct make_shared_enabler : public A {};

        return std::make_shared<make_shared_enabler>();
    }

private:
    A() {}  
};
Mark Tolley
la source
5
si Aa des constructeurs par défaut , vous devrez également les exposer: struct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };. Cela rend tous les constructeurs privés Avisibles en tant que make_shared_enablerconstructeurs. L'utilisation de la fonction d'héritage des constructeurs ( using A::A;) ne semble pas aider ici car les constructeurs seront toujours privés.
anton_rh
2
@anton_rh: vous ne pouvez pas ajouter d'arguments de modèle aux classes internes. Regardez ici .
bobbel
3
Hm ... Vous avez raison. Dans mon cas struct était pas locale, mais était un struct privé: class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }. Voir ici cpp.sh/65qbr .
anton_rh
Cela fonctionne très bien. Y a-t-il une chance d'en faire une propriété héritable, de sorte que ce modèle n'a pas à être répété plusieurs fois? En particulier, la version qui expose des constructeurs non par défaut me serait très intéressante. La version par défaut nécessiterait "simplement" une construction syntaxique qui remplace A par quelle que soit la classe qui hérite de la classe. Je ne suis au courant de rien de tel, mais je ne serais pas surpris d'apprendre que cela existe ...
Kjeld Schmidt
30

Voici une solution intéressante pour cela:

#include <memory>

class A {
   public:
     static shared_ptr<A> Create();

   private:
     A() {}

     struct MakeSharedEnabler;   
 };

struct A::MakeSharedEnabler : public A {
    MakeSharedEnabler() : A() {
    }
};

shared_ptr<A> A::Create() {
    return make_shared<MakeSharedEnabler>();
}
Mohit Aron
la source
3
J'aime ça. Cela peut être rendu un peu plus simple en définissant MakeSharedEnablerlocalement à l'intérieur A::Create().
dlf le
Super idée Mohit ça m'a beaucoup aidé.
Jnana
12

Que dis-tu de ça?

static std::shared_ptr<A> create()
{
    std::shared_ptr<A> pA(new A());
    return pA;
}
Sean
la source
13
Cela fonctionne très bien. Mais ::std::make_shareda des fonctionnalités au-delà de la simple création d'un shared_ptr à quelque chose. Il alloue le nombre de références avec l'objet afin qu'ils soient situés à proximité les uns des autres. Je veux vraiment, vraiment utiliser ::std::make_shared.
Omnifarious
Les opérateurs d'assignation et de copie supprimés interdisent cela
Dani
7
C'est vraiment l'approche la plus simple, même si ce n'est pas vraiment ce que la question posait. make_shared a quelques caractéristiques intéressantes et j'essaie de l'utiliser dans la mesure du possible, mais dans cette situation, il semble assez probable que les avantages de performance d'exécution de make_shared ne l'emportent pas sur la complexité du code supplémentaire et la cérémonie réellement nécessaires pour l'utiliser. Si vous avez vraiment besoin des performances de make_shared, devenez fou, mais ne négligez pas la simplicité d'utiliser simplement le constructeur de shared_ptr.
Kevin
Attention cependant aux fuites de mémoire ... voir cette question stackoverflow.com/a/14837300/2149539
dgmz
12
struct A {
public:
  template<typename ...Arg> std::shared_ptr<A> static create(Arg&&...arg) {
    struct EnableMakeShared : public A {
      EnableMakeShared(Arg&&...arg) :A(std::forward<Arg>(arg)...) {}
    };
    return std::make_shared<EnableMakeShared>(std::forward<Arg>(arg)...);
  }
  void dump() const {
    std::cout << a_ << std::endl;
  }
private:
  A(int a) : a_(a) {}
  A(int i, int j) : a_(i + j) {}
  A(std::string const& a) : a_(a.size()) {}
  int a_;
};
alpha
la source
C'est en grande partie la même chose que la réponse de Luc Danton, même si en faire une classe locale est une bonne idée. Une explication accompagnant le code pourrait en faire une bien meilleure réponse.
Normalement, je veux écrire une si petite fonction dans un fichier d'en-tête mais pas dans un fichier cc. Deuxièmement, en pratique, j'utilise une macro qui ressemble au modèle #define SharedPtrCreate (T) <typename ... Arg> .....
alpha
Bonne réponse. Je mettrais même cela dans une macro appelée IMPLEMENT_CREATE_SHARED (ClassName)
ivan.ukr
8

Comme je n'aimais pas les réponses déjà fournies, j'ai décidé de rechercher et j'ai trouvé une solution qui n'est pas aussi générique que les réponses précédentes mais je l'aime mieux (tm). Rétrospectivement, ce n'est pas beaucoup plus agréable que celui fourni par Omnifarius mais il pourrait y avoir d'autres personnes qui l'aiment aussi :)

Ce n'est pas moi qui l'ai inventé, mais c'est l'idée de Jonathan Wakely (développeur GCC).

Malheureusement, cela ne fonctionne pas avec tous les compilateurs car il repose sur un petit changement dans l'implémentation de std :: allocate_shared. Mais ce changement est maintenant une mise à jour proposée pour les bibliothèques standard, il pourrait donc être pris en charge par tous les compilateurs à l'avenir. Cela fonctionne sur GCC 4.7.

La demande de modification du groupe de travail sur la bibliothèque standard C ++ est ici: http://lwg.github.com/issues/lwg-active.html#2070

Le patch GCC avec un exemple d'utilisation est ici: http://old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html

La solution fonctionne sur l'idée d'utiliser std :: allocate_shared (au lieu de std :: make_shared) avec un allocateur personnalisé qui est déclaré ami à la classe avec le constructeur privé.

L'exemple de l'OP ressemblerait à ceci:

#include <memory>

template<typename Private>
struct MyAlloc : std::allocator<Private>
{
    void construct(void* p) { ::new(p) Private(); }
};

class A {
    public:
        static ::std::shared_ptr<A> create() {
            return ::std::allocate_shared<A>(MyAlloc<A>());
        }

    protected:
        A() {}
        A(const A &) = delete;
        const A &operator =(const A &) = delete;

        friend struct MyAlloc<A>;
};

int main() {
    auto p = A::create();
    return 0;
}

Un exemple plus complexe basé sur l'utilitaire sur lequel je travaille. Avec cela, je ne pouvais pas utiliser la solution de Luc. Mais celui d'Omnifarius pourrait être adapté. Ce n'est pas que, bien que dans l'exemple précédent, tout le monde puisse créer un objet A en utilisant MyAlloc dans celui-ci, il n'y a pas moyen de créer A ou B en plus de la méthode create ().

#include <memory>

template<typename T>
class safe_enable_shared_from_this : public std::enable_shared_from_this<T>
{
    public:
    template<typename... _Args>
        static ::std::shared_ptr<T> create(_Args&&... p_args) {
            return ::std::allocate_shared<T>(Alloc(), std::forward<_Args>(p_args)...);
        }

    protected:
    struct Alloc : std::allocator<T>
    {  
        template<typename _Up, typename... _Args>
        void construct(_Up* __p, _Args&&... __args)
        { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
    };
    safe_enable_shared_from_this(const safe_enable_shared_from_this&) = delete;
    safe_enable_shared_from_this& operator=(const safe_enable_shared_from_this&) = delete;
};

class A : public safe_enable_shared_from_this<A> {
    private:
        A() {}
        friend struct safe_enable_shared_from_this<A>::Alloc;
};

class B : public safe_enable_shared_from_this<B> {
    private:
        B(int v) {}
        friend struct safe_enable_shared_from_this<B>::Alloc;
};

int main() {
    auto a = A::create();
    auto b = B::create(5);
    return 0;
}
Zsolt Rizsányi
la source
6

Idéalement, je pense que la solution parfaite nécessiterait des ajouts à la norme C ++. Andrew Schepler propose ce qui suit:

(Allez ici pour tout le fil)

nous pouvons emprunter une idée à boost :: iterator_core_access. Je propose une nouvelle classe std::shared_ptr_accesssans membres publics ou protégés, et pour spécifier que pour std :: make_shared (args ...) et std :: alloc_shared (a, args ...), les expressions :: new (pv) T (forward (args) ...) et ptr-> ~ T () doivent être bien formés dans le contexte de std :: shared_ptr_access.

Une implémentation de std :: shared_ptr_access pourrait ressembler à ceci:

namespace std {
    class shared_ptr_access
    {
        template <typename _T, typename ... _Args>
        static _T* __construct(void* __pv, _Args&& ... __args)
        { return ::new(__pv) _T(forward<_Args>(__args)...); }

        template <typename _T>
        static void __destroy(_T* __ptr) { __ptr->~_T(); }

        template <typename _T, typename _A>
        friend class __shared_ptr_storage;
    };
}

Usage

Si / quand ce qui précède est ajouté à la norme, nous ferions simplement:

class A {
public:
   static std::shared_ptr<A> create() {
      return std::make_shared<A>();
   }

 protected:
   friend class std::shared_ptr_access;
   A() {}
   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

Si cela vous semble également un ajout important à la norme, n'hésitez pas à ajouter vos 2 cents au groupe Google isocpp lié.

Boris Dalstein
la source
1
Je pense que c'est un bon ajout à la norme, mais ce n'est pas assez important pour moi de prendre le temps de rejoindre le groupe Google et de commenter, puis de prêter attention à ce groupe et au commentaire. :-)
Omnifarious
4

Je me rends compte que ce fil est assez ancien, mais j'ai trouvé une réponse qui ne nécessite pas d'héritage ou d'arguments supplémentaires au constructeur que je ne pourrais pas voir ailleurs. Ce n'est cependant pas portable:

#include <memory>

#if defined(__cplusplus) && __cplusplus >= 201103L
#define ALLOW_MAKE_SHARED(x) friend void __gnu_cxx::new_allocator<test>::construct<test>(test*);
#elif defined(_WIN32) || defined(WIN32)
#if defined(_MSC_VER) && _MSC_VER >= 1800
#define ALLOW_MAKE_SHARED(x) friend class std::_Ref_count_obj;
#else
#error msc version does not suport c++11
#endif
#else
#error implement for platform
#endif

class test {
    test() {}
    ALLOW_MAKE_SHARED(test);
public:
    static std::shared_ptr<test> create() { return std::make_shared<test>(); }

};
int main() {
    std::shared_ptr<test> t(test::create());
}

J'ai testé sur Windows et Linux, il peut avoir besoin d'être peaufiné pour différentes plates-formes.

ashleysmithgpu
la source
1
Je suis tenté de le -1 par manque de portabilité. Les autres réponses (en particulier les réponses «classe clé») sont assez élégantes et la réponse non portable très laide. Je ne peux pas penser à une raison pour laquelle vous utiliseriez la réponse non portable. Ce n'est pas plus rapide ou quelque chose comme ça.
Omnifarious
@Omnifarious C'est en effet non portable et je ne le recommanderais pas, mais je pense que c'est en fait la solution sémantiquement la plus correcte. Dans ma réponse , je fais un lien vers une proposition d'ajout std::shared_ptr_accessà la norme, qui pourrait être considérée comme permettant de faire ce qui précède de manière simple et portable.
Boris Dalstein
3

Il y a un problème plus poilu et intéressant qui se produit lorsque vous avez deux classes A et B strictement liées qui fonctionnent ensemble.

Disons que A est la "classe maître" et B son "esclave". Si vous souhaitez restreindre l'instanciation de B uniquement à A, vous rendez le constructeur de B privé et l'ami B vers A comme ceci

class B
{
public:
    // B your methods...

private:
    B();
    friend class A;
};

Malheureusement, appeler à std::make_shared<B>()partir d'une méthode de Afera se plaindre du compilateur d' B::B()être privé.

Ma solution à cela est de créer une Passclasse factice publique (tout commenullptr_t ) à l'intérieur Bqui a un constructeur privé et qui est ami avec Aet rend Ble constructeur de public public et ajouter Passà ses arguments, comme ceci.

class B
{
public:
  class Pass
  {
    Pass() {}
    friend class A;
  };

  B(Pass, int someArgument)
  {
  }
};

class A
{
public:
  A()
  {
    // This is valid
    auto ptr = std::make_shared<B>(B::Pass(), 42);
  }
};

class C
{
public:
  C()
  {
    // This is not
    auto ptr = std::make_shared<B>(B::Pass(), 42);
  }
};
Keebus
la source
3

Si vous souhaitez également activer un constuctor qui prend des arguments, cela peut aider un peu.

#include <memory>
#include <utility>

template<typename S>
struct enable_make : public S
{
    template<typename... T>
    enable_make(T&&... t)
        : S(std::forward<T>(t)...)
    {
    }
};

class foo
{
public:
    static std::unique_ptr<foo> create(std::unique_ptr<int> u, char const* s)
    {
        return std::make_unique<enable_make<foo>>(std::move(u), s);
    }
protected:
    foo(std::unique_ptr<int> u, char const* s)
    {
    }
};

void test()
{
    auto fp = foo::create(std::make_unique<int>(3), "asdf");
}
démo
la source
3

[Edit] J'ai lu le fil noté ci-dessus sur une std::shared_ptr_access<>proposition standardisée . À l'intérieur, il y avait une réponse indiquant un correctif std::allocate_shared<>et un exemple de son utilisation. Je l'ai adapté à un modèle d'usine ci-dessous, et l'ai testé sous gcc C ++ 11/14/17. Cela fonctionne également avec std::enable_shared_from_this<>, donc serait évidemment préférable à ma solution originale dans cette réponse. C'est ici...

#include <iostream>
#include <memory>

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        return std::allocate_shared<T>(Alloc<T>(), std::forward<A>(args)...);
    }
private:
    template<typename T>
    struct Alloc : std::allocator<T> {
        template<typename U, typename... A>
        void construct(U* ptr, A&&... args) {
            new(ptr) U(std::forward<A>(args)...);
        }
        template<typename U>
        void destroy(U* ptr) {
            ptr->~U();
        }
    };  
};

class X final : public std::enable_shared_from_this<X> {
    friend class Factory;
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(int) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto p1 = Factory::make_shared<X>(42);
    auto p2 = p1->shared_from_this();
    std::cout << "p1=" << p1 << "\n"
              << "p2=" << p2 << "\n"
              << "count=" << p1.use_count() << "\n";
}

[Orig] J'ai trouvé une solution en utilisant le constructeur d'alias de pointeur partagé. Il permet à la fois au ctor et au dtor d'être privés, ainsi que l'utilisation du spécificateur final.

#include <iostream>
#include <memory>

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        auto ptr = std::make_shared<Type<T>>(std::forward<A>(args)...);
        return std::shared_ptr<T>(ptr, &ptr->type);
    }
private:
    template<typename T>
    struct Type final {
        template<typename... A>
        Type(A&&... args) : type(std::forward<A>(args)...) { std::cout << "Type(...) addr=" << this << "\n"; }
        ~Type() { std::cout << "~Type()\n"; }
        T type;
    };
};

class X final {
    friend struct Factory::Type<X>;  // factory access
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(...) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto ptr1 = Factory::make_shared<X>();
    auto ptr2 = Factory::make_shared<X>(42);
}

Notez que l'approche ci-dessus ne fonctionne pas bien std::enable_shared_from_this<>car l'initiale std::shared_ptr<>est au wrapper et non au type lui-même. Nous pouvons y remédier avec une classe équivalente compatible avec l'usine ...

#include <iostream>
#include <memory>

template<typename T>
class EnableShared {
    friend class Factory;  // factory access
public:
    std::shared_ptr<T> shared_from_this() { return weak.lock(); }
protected:
    EnableShared() = default;
    virtual ~EnableShared() = default;
    EnableShared<T>& operator=(const EnableShared<T>&) { return *this; }  // no slicing
private:
    std::weak_ptr<T> weak;
};

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        auto ptr = std::make_shared<Type<T>>(std::forward<A>(args)...);
        auto alt = std::shared_ptr<T>(ptr, &ptr->type);
        assign(std::is_base_of<EnableShared<T>, T>(), alt);
        return alt;
    }
private:
    template<typename T>
    struct Type final {
        template<typename... A>
        Type(A&&... args) : type(std::forward<A>(args)...) { std::cout << "Type(...) addr=" << this << "\n"; }
        ~Type() { std::cout << "~Type()\n"; }
        T type;
    };
    template<typename T>
    static void assign(std::true_type, const std::shared_ptr<T>& ptr) {
        ptr->weak = ptr;
    }
    template<typename T>
    static void assign(std::false_type, const std::shared_ptr<T>&) {}
};

class X final : public EnableShared<X> {
    friend struct Factory::Type<X>;  // factory access
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(...) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto ptr1 = Factory::make_shared<X>();
    auto ptr2 = ptr1->shared_from_this();
    std::cout << "ptr1=" << ptr1.get() << "\nptr2=" << ptr2.get() << "\n";
}

Enfin, quelqu'un a dit que clang se plaignait du fait que Factory :: Type était privé lorsqu'il était utilisé comme ami, alors rendez-le public si c'est le cas. L'exposer ne fait aucun mal.

user1715587
la source
3

J'ai eu le même problème, mais aucune des réponses existantes n'était vraiment satisfaisante car j'ai besoin de transmettre des arguments au constructeur protégé. De plus, je dois le faire pour plusieurs classes, chacune prenant des arguments différents.

À cet effet, et en me basant sur plusieurs des réponses existantes qui utilisent toutes des méthodes similaires, je présente cette petite pépite:

template < typename Object, typename... Args >
inline std::shared_ptr< Object >
protected_make_shared( Args&&... args )
{
  struct helper : public Object
  {
    helper( Args&&... args )
      : Object{ std::forward< Args >( args )... }
    {}
  };

  return std::make_shared< helper >( std::forward< Args >( args )... );
}
Matthieu
la source
1

La racine du problème est que si la fonction ou la classe que vous utilisez fait des appels de niveau inférieur à votre constructeur, elle doit également être amicale. std :: make_shared n'est pas la fonction qui appelle réellement votre constructeur, donc cela ne fait aucune différence.

class A;
typedef std::shared_ptr<A> APtr;
class A
{
    template<class T>
    friend class std::_Ref_count_obj;
public:
    APtr create()
    {
        return std::make_shared<A>();
    }
private:
    A()
    {}
};

std :: _ Ref_count_obj appelle en fait votre constructeur, il doit donc être un ami. Comme c'est un peu obscur, j'utilise une macro

#define SHARED_PTR_DECL(T) \
class T; \
typedef std::shared_ptr<T> ##T##Ptr;

#define FRIEND_STD_MAKE_SHARED \
template<class T> \
friend class std::_Ref_count_obj;

Ensuite, votre déclaration de classe semble assez simple. Vous pouvez créer une seule macro pour déclarer le ptr et la classe si vous préférez.

SHARED_PTR_DECL(B);
class B
{
    FRIEND_STD_MAKE_SHARED
public:
    BPtr create()
    {
        return std::make_shared<B>();
    }
private:
    B()
    {}
};

C'est en fait une question importante. Pour rendre le code portable maintenable, vous devez masquer autant que possible l'implémentation.

typedef std::shared_ptr<A> APtr;

cache un peu la façon dont vous gérez votre pointeur intelligent, vous devez être sûr d'utiliser votre typedef. Mais si vous devez toujours en créer un avec make_shared, cela va à l'encontre de l'objectif.

L'exemple ci-dessus force le code utilisant votre classe à utiliser votre constructeur de pointeur intelligent, ce qui signifie que si vous passez à une nouvelle saveur de pointeur intelligent, vous modifiez votre déclaration de classe et vous avez de bonnes chances d'être terminé. NE supposez PAS que votre prochain boss ou projet utilisera stl, boost, etc. pour le changer un jour.

En faisant cela pendant près de 30 ans, j'ai payé un gros prix en temps, en douleur et en effets secondaires pour réparer cela alors que cela était mal fait il y a des années.

brtip
la source
2
std::_Ref_count_objest un détail de mise en œuvre. Cela signifie que cette solution pourrait fonctionner pour vous, pour l'instant, sur votre plate-forme. Mais cela peut ne pas fonctionner pour les autres et peut cesser de fonctionner à chaque fois que votre compilateur est mis à jour ou peut-être même si vous changez simplement les indicateurs de compilation.
François Andrieux
-3

Vous pouvez utiliser ceci:

class CVal
{
    friend std::shared_ptr<CVal>;
    friend std::_Ref_count<CVal>;
public:
    static shared_ptr<CVal> create()
    {
        shared_ptr<CVal> ret_sCVal(new CVal());
        return ret_sCVal;
    }

protected:
    CVal() {};
    ~CVal() {};
};
boogie
la source
1
N'utilise pas std::make_shared.
Brian
-3
#include <iostream>
#include <memory>

class A : public std::enable_shared_from_this<A>
{
private:
    A(){}
    explicit A(int a):m_a(a){}
public:
    template <typename... Args>
    static std::shared_ptr<A> create(Args &&... args)
    {
        class make_shared_enabler : public A
        {
        public:
            make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...){}
        };
        return std::make_shared<make_shared_enabler>(std::forward<Args>(args)...);
    }

    int val() const
    {
        return m_a;
    }
private:
    int m_a=0;
};

int main(int, char **)
{
    std::shared_ptr<A> a0=A::create();
    std::shared_ptr<A> a1=A::create(10);
    std::cout << a0->val() << " " << a1->val() << std::endl;
    return 0;
}
cracher
la source
Ceci est juste un double de cette réponse: stackoverflow.com/a/27832765/167958
Omnifarious