0
\$\begingroup\$

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)?

Morwenn
20.2k3 gold badges69 silver badges132 bronze badges
asked Dec 10, 2013 at 6:56
\$\endgroup\$
2
  • \$\begingroup\$ To do what exactly - inherit privately from SharedConstructable<T> (more or less as given) and not expose a constructor? Or was that just an attempted means to accomplish another goal? \$\endgroup\$ Commented Dec 12, 2013 at 3:44
  • \$\begingroup\$ To allow construction only through shared_ptrs - It needs to inherit publicly to expose the nested Ptr class \$\endgroup\$ Commented Dec 12, 2013 at 5:06

1 Answer 1

2
\$\begingroup\$

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.

answered Dec 12, 2013 at 19:11
\$\endgroup\$
5
  • \$\begingroup\$ 1K! Congratulations! \$\endgroup\$ Commented Dec 12, 2013 at 21:46
  • \$\begingroup\$ Thats an interesting approach, but unfortunately much less portable or clean... thanks for trying though \$\endgroup\$ Commented 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\$ Commented 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\$ Commented Dec 14, 2013 at 1:35
  • \$\begingroup\$ The use of make_shared<T>(...) is preferable to shared_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\$ Commented Dec 14, 2013 at 5:51

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.