I've tried using CRTP, but the forces the class to befriend CRTP Base.
template <typename T>
class SharedConstructable
: public std::enable_shared_from_this<T>
{
typedef std::shared_ptr<T> ptr_type;
public:
struct Ptr
: ptr_type
{
template<typename... Args>
Ptr(Args&&... args)
: ptr_type(new T(args...))
{
}
};
};
class SharedOnly
: SharedConstructable<SharedOnly>
{
private:
friend class SharedConstructable<SharedOnly>;
SharedOnly(int) {}
}
Usage:
SharedOnly x(5); // Error Constructor is private
SharedOnly::Ptr(5); // Correct usage;
Is there a cleaner/better way of doing this (without the friendship)?
1 Answer 1
If I follow you correctly, you're looking for a clean way to have a private constructor that can only be invoked by std::make_shared or std::allocate_shared. Unfortunately this does not appear to be portable or easy, and I don't see a way to avoid your request to avoid friendship. However you can make the friend something that other people cannot easily co-opt. I'd call this mildly better, but it definitely has some downsides.
As this is not portable, the scenario I'm showing here is specific to the compiler I had handy: Visual Studio 2012. First I created a class like this:
class SomeClass
{
public:
int GetValue() const { return m_val; }
// optionally provide Ptr like yours?
// static std::shared_ptr<SomeClass> Ptr(int val) { return std::make_shared<SomeClass>(val); }
private:
SomeClass(int val) { m_val = val; }
SomeClass(const SomeClass&); // = delete;
SomeClass(SomeClass&&); // = delete;
~SomeClass() {}
int m_val;
};
This is enough to prevent usage like SomeClass thing(5);
Then I wanted to try to befriend std::make_shared. When I tried to compile code using this definition and std::make_shared<SomeClass>(5)
, I got an error pointing to what needed access:
error C2248 [...] cannot access private member [SomeClass::~SomeClass ...] while compiling class template member function 'void std::_Ref_count_obj<_Ty>::_Destroy(void)'
After befriending class std::_Ref_count_obj<SomeClass>
(I first tried befriending the specific method, but that created worse problems), I also decided to fix the warning by befriending std::_Get_align<SomeClass>
:
warning C4624: 'std::_Get_align<_Ty [= SomeClass]>' : destructor could not be generated because a base class destructor is inaccessible
This left me with the following additions to this class that allows std::make_shared<SomeClass>(5)
but not SomeClass obj(5)
.
class SomeClass
{
: : :
// Allow use in VS2012 implementation of std::make_shared
friend class std::_Ref_count_obj<SomeClass>;
friend struct std::_Get_align<SomeClass>;
: : :
};
I'll leave other compilers up to you as you need them. It may be worth collecting a series of macro alternatives that you can put in any class that needs this capability.
-
\$\begingroup\$ 1K! Congratulations! \$\endgroup\$Mathieu Guindon– Mathieu Guindon2013年12月12日 21:46:05 +00:00Commented Dec 12, 2013 at 21:46
-
\$\begingroup\$ Thats an interesting approach, but unfortunately much less portable or clean... thanks for trying though \$\endgroup\$nishantjr– nishantjr2013年12月13日 03:02:58 +00:00Commented Dec 13, 2013 at 3:02
-
\$\begingroup\$ @nishantjr The portability is definitely its biggest weakness. However if you hide that behind a
#define BEFRIEND_MAKE_SHARED(ClassName) ...
it should be easy to clean up in a single shared header. \$\endgroup\$Michael Urman– Michael Urman2013年12月13日 15:44:52 +00:00Commented Dec 13, 2013 at 15:44 -
\$\begingroup\$ eeek, macros are evil, especially for something as trivial as this. And how is that any beeter than the original solution I gave? \$\endgroup\$nishantjr– nishantjr2013年12月14日 01:35:33 +00:00Commented Dec 14, 2013 at 1:35
-
\$\begingroup\$ The use of
make_shared<T>(...)
is preferable toshared_ptr<T>(new T{...})
for its memory layout, and I prefer the friend statements over creating two helper classes. But as for the macro, to each his own. \$\endgroup\$Michael Urman– Michael Urman2013年12月14日 05:51:12 +00:00Commented Dec 14, 2013 at 5:51
SharedConstructable<T>
(more or less as given) and not expose a constructor? Or was that just an attempted means to accomplish another goal? \$\endgroup\$