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

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

    There's a more hairy and interesting problem that happens when you have two strictly related classes A and B that work together.

    Say A is the "master class" and B its "slave". If you want to restrict instantiation of B only to A, you'd make B's constructor private, and friend B to A like this

    class B
    {
    public:
        // B your methods...
    
    private:
        B();
        friend class A;
    };
    

    Unfortunately calling std::make_shared<B>() from a method of A will make the compiler complain about B::B() being private.

    My solution to this is to create a public Pass dummy class (just like nullptr_t) inside B that has private constructor and is friend with A and make B's constructor public and add Pass to its arguments, like this.

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

    How about this?

    static std::shared_ptr<A> create()
    {
        std::shared_ptr<A> pA(new A());
        return pA;
    }
    
    0 讨论(0)
  • 2020-11-22 09:47

    If you also want to enable a constuctor that takes arguments, this may help a bit.

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

    You can use this:

    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() {};
    };
    
    0 讨论(0)
  • 2020-11-22 09:49
    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_;
    };
    
    0 讨论(0)
  • [Edit] I read through the thread noted above on a standardized std::shared_ptr_access<> proposal. Within there was a response noting a fix to std::allocate_shared<> and an example of its use. I've adapted it to a factory template below, and tested it under gcc C++11/14/17. It works with std::enable_shared_from_this<> as well, so would obviously be preferable to my original solution in this answer. Here it is...

    #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] I found a solution using the shared pointer aliasing constructor. It allows both the ctor and dtor to be private, as well as use of the final specifier.

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

    Note that the approach above doesn't play well with std::enable_shared_from_this<> because the initial std::shared_ptr<> is to the wrapper and not the type itself. We can address this with an equivalent class that is compatible with the factory...

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

    Lastly, somebody said clang complained about Factory::Type being private when used as a friend, so just make it public if that's the case. Exposing it does no harm.

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