262 votes

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

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'ai cette erreur quand je 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 est essentiellement à dire que certains aléatoire méthode de l'instanciation d'un modèle de la pile de ::std::make_shared peut pas accéder au constructeur car il est protégé.

Mais j'ai vraiment envie d'utiliser les deux ::std::make_shared et empêcher quelqu'un d'en faire un objet de cette classe qui n'est pas pointée par un ::std::shared_ptr. Est-il un moyen pour accomplir cette?

140voto

Omnifarious Points 25666

Cette réponse est probablement meilleure, et celle que je vais probablement accepter. Mais j'ai aussi mis au point une méthode plus laide, mais qui permet de tout laisser en ligne et ne nécessite pas de classe dérivée:

 #include <memory>

class A {
   struct this_is_private {
   };

 public:
   explicit A(const this_is_private &) {}

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

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

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

100voto

Luc Danton Points 21421

En regardant les exigences pour l' std::make_shared dans 20.7.2.2.6 shared_ptr création [util.smartptr.partagé.créer], paragraphe 1:

Nécessite: L'expression ::new (pv) T(std::forward(args)...), où pv est de type void* et des points de stockage adapté pour contenir un objet de type T, doit être bien formé. Un est un allocateur (17.6.3.5). Le constructeur de copie et le destructeur de ne pas lancer des exceptions.

Car l'exigence est sans condition spécifiée dans les termes de cette expression et des choses comme champ d'application ne sont pas pris en compte, je pense à des trucs comme l'amitié sont à droite.

Une solution simple consiste à dériver de A. Ce n'est pas nécessiter A d'une 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>();
}

100voto

Mark Tolley Points 163

Peut-être la solution la plus simple. Basé sur la réponse précédente de Mohit Aron et intégrant 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() {}  
};
 

47voto

Mohit Aron Points 51

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>();
}
 

9voto

Zsolt Rizsányi Points 103

Depuis je n'aimais pas déjà fourni des réponses, j'ai décidé de recherche et 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, il n'est pas beaucoup plus agréable que celui fourni par Omnifarius mais il pourrait y avoir d'autres personnes qui aiment aussi :)

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

Malheureusement, il ne fonctionne pas avec tous les compilateurs, car elle repose sur un petit changement dans le std::allocate_shared mise en œuvre. Mais ce changement est maintenant une mise à jour proposée pour les bibliothèques standard, de sorte qu'il pourrait obtenir pris en charge par tous les compilateurs dans l'avenir. Il fonctionne sur GCC 4.7.

C++ standard Library Groupe de Travail sur la demande de modification est ici: http://lwg.github.com/issues/lwg-active.html#2070

La GCC patch 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ée ami de la classe avec le constructeur privé.

L'exemple de l'OP devrait ressembler à 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 qui est basé sur l'utilitaire, je travaille sur. Avec cela, je ne pouvais pas utiliser Luc solution. Mais l'un par Omnifarius pourraient être adaptés. Pas que, tandis que dans l'exemple précédent, tout le monde peut créer Un objet à l'aide de la MyAlloc dans celui-ci il n'y a pas moyen de créer Un 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;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X