How do I call ::std::make_shared on a class with only protected or private constructors?

前端 未结 16 2641
遥遥无期
遥遥无期 2020-11-22 09:07

I have this code that doesn\'t work, but I think the intent is clear:

testmakeshared.cpp

#include 

class A {
 public:
   stat         


        
相关标签:
16条回答
  • 2020-11-22 09:55

    I had the same problem, but none of the existing answers were really satisfactory as I need to pass arguments to the protected constructor. Moreover, I need to do this for several classes, each taking different arguments.

    To that effect, and building on several of the existing answers which all use similar methods, I present this little nugget:

    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 )... );
    }
    
    0 讨论(0)
  • 2020-11-22 09:56

    Ideally, I think the perfect solution would require additions to the C++ standard. Andrew Schepler proposes the following:

    (Go here for the whole thread)

    we can borrow an idea from boost::iterator_core_access. I propose a new class std::shared_ptr_access with no public or protected members, and to specify that for std::make_shared(args...) and std::alloc_shared(a, args...), the expressions ::new(pv) T(forward(args)...) and ptr->~T() must be well-formed in the context of std::shared_ptr_access.

    An implementation of std::shared_ptr_access might look like:

    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

    If/when the above is added to the standard, we would simply do:

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

    If this also sounds like an important addition to the standard to you, feel free to add your 2 cents to the linked isocpp Google Group.

    0 讨论(0)
  • 2020-11-22 09:56

    The root of the problem is that if the function or class you friend makes lower level calls to your constructor, they have to be friended too. std::make_shared isn't the function that's actually calling your constructor so friending it makes no difference.

    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 is actually calling your constructor, so it needs to be a friend. Since that's a bit obscure, I use a 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;
    

    Then your class declaration looks fairly simple. You can make a single macro for declaring the ptr and the class if you prefer.

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

    This is actually an important issue. To make maintainable, portable code you need to hide as much of the implementation as possible.

    typedef std::shared_ptr<A> APtr;
    

    hides how you're handling your smart pointer a bit, you have to be sure to use your typedef. But if you always have to create one using make_shared, it defeats the purpose.

    The above example forces code using your class to use your smart pointer constructor, which means that if you switch to a new flavor of smart pointer, you change your class declaration and you have a decent chance of being finished. DO NOT assume your next boss or project will use stl, boost etc. plan for changing it someday.

    Doing this for almost 30 years, I've paid a big price in time, pain and side effects to repair this when it was done wrong years ago.

    0 讨论(0)
  • 2020-11-22 09:56
    #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;
    }
    
    0 讨论(0)
提交回复
热议问题