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

20

258

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

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

But I get this error when I compile it:

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

This message is basically saying that some random method way down in the template instantiation stack from ::std::make_shared can't access the constructor because it's protected.

But I really want to use both ::std::make_shared and prevent anybody from making an object of this class that isn't pointed at by a ::std::shared_ptr. Is there any way to accomplish this?

Seftton answered 16/11, 2011 at 5:11 Comment(8)
You can mark the function deep down that needs the constructor as friend, but that won't be portable.Ingram
@Dani: Yeah, it would be nice to have a portable solution. But that would work.Seftton
Why not use return std::shared_ptr<A>(new A()), instead of return std::make_shared<A>() ?Encephalomyelitis
Because make_shared only does 1 allocation for the object and reference counts.Orientalism
@Seftton How do you think about this solution. Does it suit your need? If I miss something, please let me know.Price
@John: this is what Vassilis has proposed, if you eliminate the "enable_shared_from_this" part.Onepiece
worth reading groups.google.com/a/isocpp.org/g/std-proposals/c/…Gravely
Encapsulation is very important and it is big oversite. Is it possible to solve it at the library level?Halfhour
S
136

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.

Seftton answered 16/11, 2011 at 5:53 Comment(19)
Actually, I am a huge fan of those meaningless structures used only as keys. I prefer this to Luc's solution, but that might be my biais against inheritance.Recapitulation
Agreed, I like this better as well.Morpheme
One advantage this has is that it works even if the constructor is supposed to be private. The other only works if the constructor is protected so that a derived class has access to it.Seftton
@Berkus: Then make it protected instead of private. And by "it", I'm referring to the this_is_private class, which maybe should be renamed in such case. I usually call it constructor_access in my code.Cardamom
Ah, right, thanks dalle, I believe I was in altered state of mind when I wrote that. Seems to work fine.Jackstraws
Sadly this doesn't work if your constructor takes real parameters; in this case you can simply pass {} for the private tag without having access to the type name (tested with g++ 4.9.0). Without real parameters it tries to construct A from {}, although I have no idea why, and fails. I think making the this_is_private constructor private and providing a static method to create it fixes it, as there should be no way to access this method from the outside unless you leak the type in a member function signature.Ottoottoman
Stefan, if you give this_is_private a private ctor you can make class A a friend. Seems to close the loophole.Give
Why is this answer not ranked at the top?Cockaigne
@StevenKramer, indeed. I just change this_is_private's definition to class this_is_private { friend class A; this_is_private() {} }; and worked as expected. Note however that if this_is_private's ctor is defined as =default; the loophole is back.Theomachy
@GusevSlava - Yes, this is a variant of that idiom. That idiom is conceived of as a way to avoid giving the full privileges of friendship. But in this case it's being used to grant a specific ability when friendship isn't possible. I think this, in fact, is a better use of this idiom.Seftton
Very clever. One tweak: I'd like it more if it explicitly included the private c'tor (without the magic-class argument). Then the special public c'tor could be template <typename ... Args> explicit A(const this_is_private&, Args&&... args) : A(std::forward<Args>(args)) {}. That way you are saying that there's a private c'tor and then saying there's a special public c'tor that forwards its args.Archibaldo
@Archibaldo - That's not a bad idea. I might incorporate it into the answer.Seftton
This question already has enough alternative answers (though many of them appear to be duplicate variations of the same themes). I am using the PassKey idiom in other areas, and I found that the two top-voted answers here actually combine quite nicely. I added a private make_shared_enabler class which derives from my main class, but the main class' constructor takes a PassKey for that make_shared_enabler class. This allows me to hide the constructor from the public API by making it protected, while disallowing (additional) subclassing thanks to the PassKey.Maharajah
Why delete the copy constructor and assignment operator?Biblio
@Omnifarious: What's the point of the int in the this_is_private ctor? Why not simply explicit this_is_private() =default;Gent
@Gent - I believe that I did this out of some vague fear that somehow the default constructor could be called even if it was private. It probably should be the way you think it should be. :-)Seftton
@Biblio - Because the point is to make sure that the object must always be created on the heap with a shared_ptr pointing to it.Seftton
@Seftton For Luc's solution, the declaration and implementation of A should not be seen by the users. In other words, they should be both in cpp files. Am I right?Price
Not ugly to me. I implement multiple patterns with similar trick.Calculate
S
100

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>();
}
Shotton answered 16/11, 2011 at 5:39 Comment(14)
Oh, that's a very clever answer, and possibly better than another one I had thought of.Seftton
One question though, won't the shared_ptr delete an A and not a concrete_A, and couldn't this cause problems?Seftton
Ahh, it's because shared_ptr stores a deleter at the time of instantiation, and if you're using make_shared the deleter absolutely has to be using the right type.Seftton
I'm accepting your answer because it is a good answer. And while I think I like mine better, I think it's cheesy to accept your own answer when there is a perfectly acceptable answer that someone else made.Seftton
@LucDanton Question is not about interfaces, as the title suggests he is also asking for a private ctor. Also, that's why I'm on this question anyway. Some old code having machiavelli classes, which has a private ctor and a create method returning a raw pointer, and I am trying to convert them to smart pointers.Bake
@Bake I intended to mean interface in the broader meaning (e.g. class interface), although unfortunately I do use it with the narrower meaning in my answer. My point being that having a private member that you can use from non-friend code is a self-defeating goal by definition. It’s working as intended.Shotton
@LucDanton Though I should admit that it is weird to have a public method for a private member it is needed for some loopholes in the languageBake
@LucDanton Mistakes were madeBake
I like this approach (using it myself) but you do need a virtual destructor. It extends well to constructors with arguments (just provide a passthrough constructor). And if you are using protected rather than private you can make it completely invisible to users of the header.Beal
But doesn't this mean that theoretically anyone who can derive from A can make instances of it ? I mean, the initial intent was to prevent anyone from creating instances of A other than via create method. This solution is nice and all, but the very fact that it exists makes this whole idea flawed, or am I wrong ?Irena
@Irena yes, but that's the same situation as in the question. That being said, this solution can also fit cases where A is only a public interface and all private or protected code moves to the concrete_A implementation.Shotton
@LucDanton Yes that is true, this loophole exists also in the situation from the original question. Therefore I think workarounds like this one are pointless when the very concept they are trying to enable is flawed. To close the loophole, A has to be made final, but that in turn disallows this solution ... Not trying to discredit your answer, just wanted to point this out. I think a better way to go about implementing what the OP wanted is to use PassKey or intrusive pointers ...Irena
@LucDanton Do you mean the declaration and implementation of A should not be seen by the users? In other words, they should be both in cpp files. Am I right?Price
@LucDanton The user still could call concrete_A a; to create a new instance, which is not what the poster wants, you see he mark the constructor as private method. How do you think about it?Price
S
98

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() {}  
};
Struve answered 31/7, 2014 at 22:4 Comment(6)
if A has non-default constructors you will also need to expose them: struct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };. This makes all private constructors of A visible as make_shared_enabler constructors. Using constructors inheritance feature (using A::A;) seems doesn't help here because constructors will be still private.Turman
@anton_rh: you can't add template arguments to inner classes. See here.Ponytail
Hm... Seems you are right. In my case struct was not local, but was a private struct: class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }. See here cpp.sh/65qbr.Turman
This works great. Is there any chance of making this an inheritable property, so this pattern doesn't have to be repeated multiple times? Particularly the version that exposes non-default constructors would be very interesting to me. The default version would "merely" require some syntactic construct that replaces A with whatever the class is which inherits the class. I'm not aware of anything like that, but I wouldn't be surprised to learn that it exists...Leath
@KjeldSchmidt see my answer for a factorization of this pattern, at the cost of having protected ctors, not private ctors.Touraco
@Ponytail @Turman was right about the non-default constructor case. I also encountered the nested class problem when I tried to apply the template solution. I've solved it by directly referencing the constructor I was trying to use. In my case, it was like ServiceSlot(const uint16_t slotID). So, the simple solution was to add struct MakeSharedEnabler : public ServiceSlot { MakeSharedEnabler(const uint16_t slotID) : ServiceSlot(slotID) {}};Girdle
S
47

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>();
}
Shih answered 6/1, 2014 at 23:30 Comment(6)
I like this. It can be made a little simpler by defining MakeSharedEnabler locally inside A::Create().Unitive
Awesome idea Mohit it helped me a lot.Manis
@Unitive No you can't do this for non-template class as in an example. A class will be incomplete.Bifarious
@Unitive I did misread this, thought you mean defining MakeSharedEnabler in A. Thou you can't define MakeSharedEnabler inside Create either because it aint eclosing class A, you will get compile error. You should define MakeSharedEnabler before Create in order to make this work. Also you can't define Create inside class A as you will need MakeSharedEnabler definition to be able to convert MakeSharedEnabler into A, you do can return shared_ptr< MakeSharedEnabler> from Create, but this will be a bit uglier for client of the code and somewhate breaking encapsulation.Bifarious
For template <class T> class A; you do can define both MakeSharedEnabler and Create inside A.Bifarious
This should be the accepted answer, such simplerLukasz
T
18

How about this?

static std::shared_ptr<A> create()
{
    std::shared_ptr<A> pA(new A());
    return pA;
}
Toshiatoshiko answered 16/11, 2011 at 5:21 Comment(6)
That works great. But ::std::make_shared has functionality above and beyond simply making a shared_ptr to something. It allocates the reference count along with the object so they're located close to each other. I really, really want to use ::std::make_shared.Seftton
The deleted assigment and copy operators forbid thisIngram
This is really the most straightforward approach, even though it isn't really what the question was asking. make_shared does have some nice characteristics and I try to use it wherever possible, but in this situation it seems quite likely that the run-time performance advantages of make_shared do not outweigh the extra code complexity and ceremony actually required to use it. If you really need the performance of make_shared then go crazy, but don't overlook the simplicity of just using shared_ptr's constructor.Habiliment
Be careful about memory leaks though... see this question https://mcmap.net/q/88601/-is-it-better-to-use-shared_ptr-reset-or-operatorAnitraaniweta
@Seftton where is this documented?Congou
@Congou - en.cppreference.com/w/cpp/memory/shared_ptr/make_shared#Notes - See the notes section, the first bullet point. It's a suggested implementation in the standard.Seftton
F
18
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_;
};
Foxglove answered 8/1, 2015 at 4:7 Comment(4)
This is largely the same thing as Luc Danton's answer, although turning it into a local class is a nice touch. Some explanation to accompany the code could make this a much better answer.Tamra
Normally, I want to write such small function in header file but not cc file. Second, in practice, I use a macro which looks like #define SharedPtrCreate(T) template<typename ...Arg>.....Foxglove
Good answer. I'd even put that into a macro called like IMPLEMENT_CREATE_SHARED(ClassName)Hui
One issue with this is that now it is a little bit more confusing what parameters create actually takes, and any errors will result in more confusing template messages. It's just one tradeoff from defining the helper class inside the actual create method.Haggis
E
16

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.

Egon answered 10/7, 2018 at 13:14 Comment(0)
C
9

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;
}
Chatav answered 5/7, 2012 at 12:16 Comment(0)
M
9

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 )... );
}
Multimillionaire answered 19/6, 2019 at 22:9 Comment(2)
This is very cool, because it doesn't clutter the class. Thanks. If I make this protected_make_shared a public function in the global namespace, I had to add it as friend to the class like so: template<typename Object, typename... Args> friend shared_ptr<Object> protected_make_shared(Args&&... args);. But if this is only used by one class, you may add it as a (public) class member as static inline.Hypoblast
The downside of this solution is that protected_make_shared can be called from anywhere, including from a class which is not friend with Object.Touraco
C
5

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);
  }
};
Coomb answered 26/11, 2013 at 23:18 Comment(0)
F
5

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<x>::construct<test>(x*);
#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.

Fibrinolysis answered 10/8, 2015 at 12:39 Comment(3)
I'm tempted to -1 it for lack of portability. The other answers (particularly the 'key class' answers) are fairly elegant and the non-portable answer very ugly. I can't think of a reason you'd use the non-portable answer. It's not faster or anything like that.Seftton
@Seftton It is indeed non-portable and I wouldn't recommend, but I believe this is in fact the semantically most correct solution. In my answer, I link to a proposal of adding std::shared_ptr_access to the standard, which could be seen as allowing to do the above in a simple and portable way.Egon
You can do something similar by friending std::construct_at instead nowadays. However both these approaches leak in that clients have public access to std::construct_at and __gnu_cxx::new_allocatorSheik
G
4

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");
}
Gastro answered 5/1, 2017 at 12:22 Comment(0)
T
4

CRTP based solution that allows factorization for multiple classes, is easily enabled and works for constructors with arguments. It requires the constructor to be protected (not private). Usage is a bit similar to enable_shared_from_this. It does not have the downside of breaking the protected keyword i.e. classes that use ::make_unique will have to be friend. Inspired by the answer from Mark Tolley.

Implementation:

template <typename ClassWithProtectedCtor>
class enable_protected_make_unique
{
protected: // important, if public then equivalent to having the constructor public which is what we want to avoid!
    template <typename... Args>
    static std::unique_ptr<ClassWithProtectedCtor> make_unique(Args &&... args)
    {
        class make_unique_enabler : public ClassWithProtectedCtor
        {
        public:
            // it's from this line that comes the need to have the constructor protected, not private:
            make_unique_enabler(Args &&... args) : ClassWithProtectedCtor(std::forward<Args>(args)...) {}
        };
        return std::make_unique<make_unique_enabler>(std::forward<Args>(args)...);
    }
};

Example of usage:

class Factory;

class MyClassWithProtectedCtor : public enable_protected_make_unique<MyClassWithProtectedCtor>
{
friend Factory;
private:
    MyClassWithProtectedCtor(int a, double c) {};
}

class Factory
{
    std::unique_ptr<MyClassWithProtectedCtor> CreateMyClassWithProtectedCtor(int a, double c)
    {
        return MyClassWithProtectedCtor::make_unique(a, c);
    }
}

You can replace unique by shared, or combine both in the same "enabler" class.

Disclaimer: I have not tested that in production code, there may be downsides (such as longer error messages when the type MyClassWithProtectedCtor is mentioned).

Touraco answered 4/8, 2022 at 13:26 Comment(2)
You can make this solution more foolproof by adding static_assert(!std::is_constructible_v<ClassWithProtectedCtor, Args...>); to make_unique_enabler constructor. It will fail if the ClassWithProtectedCtor has its ctor public.Antidote
A downside of this approach is that it prevents a class from being declared final.Mouflon
M
3

[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.

Mozart answered 18/11, 2018 at 0:17 Comment(0)
P
1

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.

Plugugly answered 31/7, 2018 at 18:23 Comment(1)
std::_Ref_count_obj is an implementation detail. That means while this solution might work for you, for now, on your platform. But it might not work for others and might stop working any time your compiler updates or maybe even if you just change compilation flags.Mod
S
0
class A  {
public:
 static std::shared_ptr<A> getA() {
    return std::shared_ptr<A> a(new A());
 }

private:
  A() {}
};

Since std::make_shared cannot call the private constructor we are making an instance of A manually with new. We then set the shared_ptr to point to the new A object in its constructor. You dont have to worry about leaking memory the shared_ptr will delete A for you.

Subdivision answered 20/9, 2021 at 16:35 Comment(4)
Whilst this might provide a good answer, it's encouraged to leave some description about why it answers the question. Thank you.David
This doesn't use make_shared and so isn't an answer. There is a specific reason to use make_shared because it only does one allocation for both the object and the reference count. It also localizes access to the reference count to memory next to where the memory for the object is, improving processor caching and making it much more likely that the reference and the object are on the same VM page.Seftton
This method only does one allocation for both the object and the reference count. Shared pointers store things on the heap much like new does. I think you are making wrong making assumptions. There is no reason why the memory location using new is less efficient then using make_sharedSpruill
@Seftton is correct. The expression new A() cannot know that the object that is being allocated on the heap will be used with shared_ptr, so it cannot allocate memory for the reference counter, mutex and other parts of the implementation of shared_ptr. It only allocates an A on the heap, returns a raw pointer, and the shared_ptr constructor then must make another allocation on the heap for the reference counter.Mouflon
T
0

If possible you could create a public move constructor like so:

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

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

::std::shared_ptr<A> foo()
{
   return A::create();
}
Tilth answered 14/4, 2022 at 20:19 Comment(1)
This only works, because the move constructor is public and the type is movable, which may be undesirable or impossible.Mouflon
P
-1

How about this solution, it's simple and it also could achieve the goal. Here is the code snippet:

#include <iostream>
#include <memory>
 
class Foo : public std::enable_shared_from_this<Foo> {
private:     //the user should not construct an instance through the constructor below.                    
    Foo(int num):num_(num) { std::cout << "Foo::Foo\n"; }
public:
    Foo(const Foo&) = delete;
    Foo(Foo&&) = default;
    Foo& operator=(const Foo&) = delete;
    Foo& operator=(Foo&&) = default;

public:
    ~Foo() { std::cout << "Foo::~Foo\n"; } 

    int DoSth(){std::cout << "hello world" << std::endl; return 0;}

    std::shared_ptr<Foo> getPtr() { return shared_from_this();}

    static std::shared_ptr<Foo> Create() {
        Foo* foo = new Foo(5);
        return std::shared_ptr<Foo>(foo);
    }

private:
    int num_;

};

int main()
{
    auto sp = Foo::Create();
    sp->DoSth();

    Foo& foo = *sp.get();
    auto sp1 = foo.getPtr();

    std::cout << sp.use_count() << std::endl;
}
Price answered 11/6, 2022 at 1:54 Comment(2)
The downside of this solution is that it amounts to making the constructor public: non-friend classes can create new instances via Create(), so why not simply make the constructor public in this case ? An alternative solution that does not have this problem.Touraco
There is a reason I want to use make_shared rather than shared_from_this. It avoids an allocation for the reference count. Additionally, the reference count ends up being stored near the referenced object.Seftton
G
-1

Just use the "old" way

return std::shared_ptr<A>(new A());

I recently had to do the same in my project https://github.com/iyadahmed/geobox/blob/33472ddc596f47a4ac006fa69d8e3de97938187f/src/object.cpp#L39

Gravely answered 3/2 at 5:4 Comment(1)
Note that OP explicitly said "But I really want to use both ::std::make_shared". So this doesn't actually answer the question.Pose
G
-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;
}
Gardie answered 7/9, 2016 at 8:18 Comment(1)
This is just a duplicate of this answer: https://mcmap.net/q/87713/-how-do-i-call-std-make_shared-on-a-class-with-only-protected-or-private-constructorsSeftton

© 2022 - 2024 — McMap. All rights reserved.