When a class is intended to be used only from std::shared_ptr, for instance when using std::enable_shared_from_this, it can be desirable to enforce this by making constructors and destructors private or protected. A working example:class SharedClass :public std::enable_shared_from_this<SharedClass>{public:static std::shared_ptr<SharedClass> create(){ return { new SharedClass, [](SharedClass* p) { delete p; } }; }private:SharedClass() = default;~SharedClass() = default;SharedClass(const SharedClass&) = delete;SharedClass& operator=(const SharedClass&) = delete;};However, this example misses out on the benefits of std::make_shared, like doing just one allocation for both the control block and the pointed-to object. We might like to rewrite the body of create() as { return std::make_shared<SharedClass>(); }, but this is invalid as the required constructor and destructor are inaccessible. This can't be solved with a friend declaration.
I know that this thread is one year old, but I'd just like to say that I find this proposal by Andrew extremely useful and elegant. The alternatives are:- Simply keep the constructors public, and rely on the developers not to call them. Possibly error-prone on big projects.- Keep the constructors public but using a PassKey idiom as suggested by Nicol. This prevents developer mistakes, but still clutters the public interface with what is supposed to be an implementation detail (the constructors are still publicly visible on Doxygen, etc.)- Make the constructors private, and in the implementation of SharedClass::create() use an "enable_make_shared" nested class. This keeps the public interface clean but seems an abuse of inheritance and in theory requires the base class to have a virtual destructor.
e.g. Using his SharedClass example above, this works for the create() function:
In any case, not many lines of code to achieve the same thing (private constructors, single allocation shared_ptr).
On Thursday, July 12, 2018 at 10:34:56 AM UTC-4, Glen Fernandes wrote:A key type would be easier to use.There are advantages to both, depending on what the user needs: For example, if the user wanted SharedClass to be Trivial, they wouldn't be able to do that by providing a SharedClass constructor that accepted an argument of a key type. While SharedClass() = default; with a custom allocator and std::allocate_shared would meet that requirement.What good is that? If the default constructor isn't public, then nobody can actually trivially construct one.
And if you use some creation function or even `allocator::construct` to create it, it will almost certainly perform value initialization, not leave the type uninitialized.
On Thursday, July 12, 2018 at 11:24:45 AM UTC-4, Nicol Bolas wrote:
On Thursday, July 12, 2018 at 10:34:56 AM UTC-4, Glen Fernandes wrote:A key type would be easier to use.There are advantages to both, depending on what the user needs: For example, if the user wanted SharedClass to be Trivial, they wouldn't be able to do that by providing a SharedClass constructor that accepted an argument of a key type. While SharedClass() = default; with a custom allocator and std::allocate_shared would meet that requirement.What good is that? If the default constructor isn't public, then nobody can actually trivially construct one.Certain C++ library implementations (including C++ standard library implementations) special case trivial types. For example: libstdc++ will only use __builtin_memmove in algorithms like std::copy when T is trivial (not just if T is trivially-copy-assignable).
In such cases, std::copy on SharedClass* defined with a defaulted constructor that preserves triviality could benefit from that.
It should be based on TriviallyCopyable, not Trivial. As I understand it, that's how it works in MSVC's standard library.
class SharedBase
: public std::enable_shared_from_this<SharedBase>
{
protected:
class CtorKey {
CtorKey() = default;
friend SharedBase;
};
public:
SharedBase(const SharedBase&) = delete;
SharedBase& operator=(const SharedBase&) = delete;
virtual ~SharedBase() = default;
template <typename... Arg>
static auto create(Arg&& ... arg)
-> std::enable_if_t<std::is_constructible_v<SharedBase, CtorKey, Arg...>,
std::shared_ptr<SharedBase>>
{ return std::make_shared<SharedBase>(ctor_key(), std::forward<Arg>(arg)...); }
virtual std::shared_ptr<SharedBase> clone() const
{ return create(*this); }
// Effectively protected:
explicit SharedBase(CtorKey) : m_a(), m_b(0) {}
SharedBase(CtorKey, const SharedBase& src) : m_a(src.m_a), m_b(src.m_b) {}
SharedBase(CtorKey, std::string a, int b=0) : m_a(std::move(a)), m_b(b) {}
protected:
static CtorKey ctor_key() { return {}; }
private:
std::string m_a;
int m_b;
};
class SharedDerived final
: public SharedBase
{
public:
template <typename... Arg>
static auto create(Arg&& ... arg)
-> std::enable_if_t<std::is_constructible_v<SharedDerived, CtorKey, Arg...>,
std::shared_ptr<SharedDerived>>
{ return std::make_shared<SharedDerived>(ctor_key(), std::forward<Arg>(arg)...); }
std::shared_ptr<SharedBase> clone() const override
{ return create(*this); }
// Effectively private (?):
explicit SharedDerived(CtorKey key) : SharedBase(key), m_c(), m_d(0) {}
SharedDerived(CtorKey key, std::string a, int b, std::string c, int d)
: SharedBase(key, std::move(a), b), m_c(std::move(c)), m_d(d) {}
private:
std::string m_c;
int m_d;
};
I don't agree that the key workaround is just as easy as the allocator one. The key way gets rather messy when you want the ability to copy the object and/or for the class to be a polymorphic base class. If we want both, things start looking like this:
So I like the allocator pattern better than the key pattern.
The friend proposal would make things even simpler than the allocator pattern, but I'm pretty satisfied with just recommending the allocator pattern for this sort of problem.