There is on old trick to give limited permission to another function to make an object; you pass a token.
struct Example_shared_only {
private:
// permission token. explicit constructor ensures
// you have to name the type before you can create one,
// and only Example_shared_only members and friends can
// name it:
struct permission_token_t {
explicit permission_token_t(int) {}
};
public:
// public ctor that requires special permission:
Example_shared_only( permission_token_t ) {}
// delete special member functions:
Example_shared_only()=delete;
Example_shared_only(Example_shared_only const&)=delete;
Example_shared_only(Example_shared_only &&)=delete;
Example_shared_only& operator=(Example_shared_only const&)=delete;
Example_shared_only& operator=(Example_shared_only &&)=delete;
// factory function:
static std::shared_ptr<Example_shared_only>
make_shared() {
return std::make_shared<Example_shared_only>( permission_token_t(0) );
}
};
now Example_shared_only::make_shared()
returns a shared_ptr
that was created with make_shared
, and nobody else can do much with it.
If you have access to more modern dialects of C++ we can do better than that:
template<class F>
struct magic_factory {
F f;
operator std::invoke_result_t<F const&>() const { return f(); }
};
struct Example2 {
static std::shared_ptr<Example2> make() {
return std::make_shared<Example2>( magic_factory{ []{ return Example2{}; } } );
}
private:
Example2() = default;
};
Live example.
This requires c++17 for guaranteed elision.
magic_factory
can be cast into whatever your factory function produces, and with guaranteed elision builds that object in-place. It has fancier uses in other contexts, but here it lets you export the constructor to make shared.
The lambda passed to magic_factory
is an implicit friend of Example2
, which gives it access to the private ctor. Guaranteed elision means a function with the signature ()->T
can be called to create a T
"in place" without any logical copies.
make_shared<T>
attempts to construct its T
using the argument. C++ examines operator T
while this happens; our magic_factory
has one such operator T
. So it is used.
It does something like
::new( (void*)ptr_to_storage ) Example2( magic_factory{ lambda_code } )
(If you are unfamiliar, this is known as "placement new" -- it states "please build an Example2
object at the location pointed to by ptr_to_storage
).
The beauty of guaranteed elision basically passes into the lambda_code
the address where the Example2
is being created (aka ptr_to_storage
), and the object is constructed right there.