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

前端 未结 16 2639
遥遥无期
遥遥无期 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:31

    Possibly the simplest solution. Based on the previous answer by Mohit Aron and incorporating dlf's suggestion.

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

    Here's a neat solution for this:

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

    This answer is probably better, and the one I'll likely accept. But I also came up with a method that's uglier, but does still let everything still be inline and doesn't require a derived class:

    #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: I changed this to make it clear that this idea is clearly and simply extensible to constructors that take arguments because other people were providing answers along those lines and seemed confused about this.

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

    Since I didn't like the already provided answers I decided to search on and found a solution that is not as generic as the previous answers but I like it better(tm). In retrospect it is not much nicer than the one provided by Omnifarius but there could be other people who like it too :)

    This is not invented by me, but it is the idea of Jonathan Wakely (GCC developer).

    Unfortunately it does not work with all the compilers because it relies on a small change in std::allocate_shared implementation. But this change is now a proposed update for the standard libraries, so it might get supported by all the compilers in the future. It works on GCC 4.7.

    C++ standard Library Working Group change request is here: http://lwg.github.com/issues/lwg-active.html#2070

    The GCC patch with an example usage is here: http://old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html

    The solution works on the idea to use std::allocate_shared (instead of std::make_shared) with a custom allocator that is declared friend to the class with the private constructor.

    The example from the OP would look like this:

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

    A more complex example that is based on the utility I'm working on. With this I could not use Luc's solution. But the one by Omnifarius could be adapted. Not that while in the previous example everybody can create an A object using the MyAlloc in this one there is not way to create A or B besides the create() method.

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

    Looking at the requirements for std::make_shared in 20.7.2.2.6 shared_ptr creation [util.smartptr.shared.create], paragraph 1:

    Requires: The expression ::new (pv) T(std::forward<Args>(args)...), where pv has type void* and points to storage suitable to hold an object of type T, shall be well formed. A shall be an allocator (17.6.3.5). The copy constructor and destructor of A shall not throw exceptions.

    Since the requirement is unconditionally specified in terms of that expression and things like scope aren't taken into account, I think tricks like friendship are right out.

    A simple solution is to derive from A. This needn't require making A an interface or even a polymorphic type.

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

    I realise this thread is rather old, but I found an answer that does not require inheritance or extra arguments to the constructor that I couldn't see elsewhere. It is not portable though:

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

    I have tested on windows and linux, it may need tweaking for different platforms.

    0 讨论(0)
提交回复
热议问题