Having an object only be constructible on the heap

89 views
Skip to first unread message

martin...@gmx.de

unread,
Jan 22, 2016, 5:40:07 PM1/22/16
to ISO C++ Standard - Discussion
I asked myself if it was possible to write a class which objects could only be created on the stack. This resulted in this question.

I'll repeat the code in question:

#include <cstddef>
#include <iostream>

class Foo {
public:
 
static Foo createOnStack() { return {}; }
 
~Foo () { std::cout << "Destructed " << --i << std::endl; }
private:
 
static int i;
 
Foo () { std::cout << "Created " << i++ << std::endl; }
 
Foo (const Foo &) = delete;
};

int Foo::i = 0;

As you can see, both the move and the copy constructor are deleted, but conveniently, you can still manage to capture the object produced by createOnStack() like so:

Foo && a = Foo::createOnStack();
const Foo& b = Foo::createOnStack();

This allows me to restrict an object of Foo to be constructed on stack only

1) You can only access it via Foo::createOnStack() and only store a const-lvalue or rvalue reference to it.
2) That means it is deconstructed always at the end of the scope at which Foo::createOnStack() was called.
3) Instances are thus always destructed in the reverse order they are constructed

The problem I have is that someone in the comment told me that the standard committee were on their way to make this

new Foo(Foo::createOnStack());

legal code. I advise against it, as this (as shown) may break the consistency of existing code. I'd like to hear your comments about the issue, and if there exists a possibly better maintainable solution to make a class that fulfills (3).

David Krauss

unread,
Jan 22, 2016, 10:10:22 PM1/22/16
to std-dis...@isocpp.org
On 2016–01–23, at 6:40 AM, martin...@gmx.de wrote:

I asked myself if it was possible to write a class which objects could only be created on the stack. This resulted in this question.

This is the scope guard idiom. A primary motivation of the guaranteed copy elision extension (which you mention, it’s P0135) is to allow non-movable scope guards without relying on temporary lifetime extension, i.e. declared with auto not auto&&, to reduce the “almost” in the “almost always auto” rule. Allowing guard objects on the heap is an unintended side-effect, but it’s not clear that it’s so bad. Anyone using new so indiscriminately much has bigger problems (and AAA is intended for just such users).

I wrote a proposal N4149 which creates a decoration for scope guard classes among others. EWG recommended splitting the scope guard and expression template parts out of it, and I’ll probably do so eventually if nobody else preempts me. That proposal doesn’t say anything about new-expressions, but the revision should. And, to achieve your goal given P0135, declaring a NSDM of guard type also needs to be forbidden. That changes the flavor of the proposal somewhat.

In-place initialization of the return value by conversion from a braced-init-list was a useful idiom, as your example shows. However, the committee tended to see it as esoteric and inconsistent. Guaranteed copy elision (such that the move constructor isn’t used) is diametrically opposed to guaranteed placement on the stack. It would take quite a push to shift the current momentum in the other direction.

Greg Marr

unread,
Jan 23, 2016, 11:11:48 AM1/23/16
to ISO C++ Standard - Discussion
On Friday, January 22, 2016 at 5:40:07 PM UTC-5, martin...@gmx.de wrote:
I asked myself if it was possible to write a class which objects could only be created on the stack. This resulted in this question.

Assuming that the subject (only be constructible on the *heap*) is incorrect, and you meant stack,
what I've done in the past is delete member operator new (and before =delete, make it private).
It does protect against accidental use, but there may be ways around it.

Nicol Bolas

unread,
Jan 23, 2016, 12:00:19 PM1/23/16
to ISO C++ Standard - Discussion

Well, that won't protect against any form of placement new. You could consider direct use of placement new to be a form of perfidy. But it's very easy to accidentally use placement new by proxy.

For example: `make_shared` is defined in terms of placement new. Also, the default `allocator_traits<Alloc>::construct` is defined in terms of placement new if your allocator does not define a `construct` member. And `std::allocator<T>::construct` will use placement new too. So every container without a special allocator that defines `construct` can heap-allocate your objects.

Removing `operator new` is a good try, but it's hardly effective at preventing accidental misuse.

Note that `make_unique` is defined to use non-placement `new`, so your code should at least prevent `make_unqiue` from working.

Martin Molzer

unread,
Jan 23, 2016, 9:02:40 PM1/23/16
to std-dis...@isocpp.org
Oops, my bad. Yes I meant to write stack there, heap-only is another topic, but could be done more easily.

David Krauss

unread,
Jan 23, 2016, 9:54:19 PM1/23/16
to std-dis...@isocpp.org

On 2016–01–24, at 12:11 AM, Greg Marr <greg...@gmail.com> wrote:

Assuming that the subject (only be constructible on the *heap*) is incorrect, and you meant stack,
what I've done in the past is delete member operator new (and before =delete, make it private).
It does protect against accidental use, but there may be ways around it.

Qualifying the new expression as ::new foo(args) will skip the member operator new. Parts of the standard library including std::allocator and shared_ptr use qualified lookup of operator new and so implement this bypass.

Greg Marr

unread,
Jan 25, 2016, 4:15:19 PM1/25/16
to ISO C++ Standard - Discussion
I think it did prevent creating a std::vector<> of them, because we had to relax that later, but that may have just been because of how we were trying to use it, and the particular compiler/library we were using at the time.

So it's only effective at very simple accidental misuse.  Oh well.

Reply all
Reply to author
Forward
0 new messages