I want to force my object to be on the stack to enforce very strict semantics and address some lifetime concerns. I've read a couple articles on how to do this, and arrived at making operator new
private (or deleted). This seems to work as expected when new
is used directly, but make_shared
compiles fine.
#include <boost/smart_ptr.hpp>
class A
{
private:
void *operator new( size_t );
void operator delete( void* );
void *operator new[]( size_t );
void operator delete[]( void* );
};
int main()
{
// A* a = new A; // Correctly produces compile error
boost::shared_ptr<A> a2 = boost::make_shared<A>();
}
Using new A
directly gives me this error as expected:
error: ‘static void* A::operator new(size_t)’ is private
I am guessing that make_shared
is working because it is using the placement new operator, but I couldn't find any articles that discussed how to prohibit this. The best solution I've come up with is to explicitly delete a template specialization for make_shared
namespace boost
{
template<>
shared_ptr<A> make_shared<A>() = delete;
};
This is obviously very specific to boost::make_shared
though. Is this really the best way?
One reason is because make_shared allocates the reference count together with the object to be managed in the same block of memory.
Using make_shared makes a difference because it could prolong the life-time of the memory allocated for the managed object. When the shared_ptr count hits 0, the destructor for the managed object gets called regardless of make_shared , but freeing its memory can only be done if make_shared was not used.
make_shared is exception-safe. It uses the same call to allocate the memory for the control block and the resource, which reduces the construction overhead. If you don't use make_shared , then you have to use an explicit new expression to create the object before you pass it to the shared_ptr constructor.
The advantage of boost::make_shared() is that the memory for the object that has to be allocated dynamically and the memory for the reference counter used by the smart pointer internally can be reserved in one chunk.
Placement forms of new
are pretty easy to handle -- they just come with extra arguments. For example, the simple placement form is
void* operator new(std::size_t, void*);
Note that 18.6.1.3 prohibits redefining these at global scope; however there should be no problem with redefining (or deleting/making inaccessible) them for your particular type.
Unfortunately, make_shared
uses scoped ::new (pv) T(std::forward<Args>(args)...)
. And as I mentioned, you aren't allowed to mess with the global placement new. So you can't prevent it at compile-time, and any runtime trap would be a hack (checking the this
pointer to see whether it's in-bounds of the stack).
You cannot enforce that objects of a class are always on the stack by making any operators inaccessible only: Any object which can be constructed on the stack can also be embedded as a member into another object. Even though your original class might struggle against being allocated on the heap the containing class won't. I'd think this is what happens in the case of boost::make_shared()
: Internally it probably allocates some record containing both its administration data plus the object actually being allocated. Alternatively, it may use allocation function from some sort of allocator which don't map to the type's operator new()
but use its own operator new()
overload instead.
I'm not sure if it is possible to prevent heap allocations (at least, when the object is embedded into another object) but any approach doing so would need to make the constructors inaccessible (most likely private
) and use some sort of factory functions, possibly in combination with moving. On the other hand, if you can move an object you have an accessible constructor and nothing prevents the object from being moved into an object on the heap.
If you specifically want to prevent the use of std::make_shared()
for a concrete type (or boost::make_shared()
although I can't quote the rules for specializing the latter), you can specialize std::make_shared()
: According to 17.6.4.2.1 [namespace.std] paragraph 1 a user is allowed to specialize any template (unless otherwise specified) if it involves a user-defined type. Thus, you can prevent A
from being used with std::make_shared()
:
class A
{
public:
A();
A(int);
};
namespace std
{
template <> std::shared_ptr<A> make_shared<A>() = delete;
template <> std::shared_ptr<A> make_shared<A, int>(int&&) = delete;
}
namespace boost
{
template <> boost::shared_ptr<A> make_shared<A>() = delete;
template <> boost::shared_ptr<A> make_shared<A, int>(int&&) = delete;
}
Obviously, if you have multiple constructors in A
you might need to add more specializations. ... and if your type happens to be a class template or your constructor to be a template, you'll be out of luck: You cannot partially specialize function templates.
With respect to your question about placement new
(which may or may not be used by make_shared()
): The placement new
(and delete
) signatures are these:
void* operator new(size_t, void*) noexcept;
void* operator new[](size_t, void*) noexcept;
void operator delete(void*, void*) noexcept;
void operator delete[](void*, void*) noexcept;
(see 18.6 [support.dynamic] paragraph 1). I doubt that making them inaccessible will help you anything, though.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With