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

\$\endgroup\$
  • \$\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\$ – Michael Urman Dec 12 '13 at 3:44
  • \$\begingroup\$ To allow construction only through shared_ptrs - It needs to inherit publicly to expose the nested Ptr class \$\endgroup\$ – nishantjr Dec 12 '13 at 5:06
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.

\$\endgroup\$
  • \$\begingroup\$ 1K! Congratulations! \$\endgroup\$ – Mathieu Guindon Dec 12 '13 at 21:46
  • \$\begingroup\$ Thats an interesting approach, but unfortunately much less portable or clean... thanks for trying though \$\endgroup\$ – nishantjr Dec 13 '13 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 Dec 13 '13 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 Dec 14 '13 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\$ – Michael Urman Dec 14 '13 at 5:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.