1

How can one create a C++ class whose objects can be put only on the stack, as single instances, but

  • not into a std::vector (or other std container) and also not in a plain array no matter where these are allocated.
  • not on the heap (answer linked to)
  • not as member into another object, if that object itself lives in a container or in the free store.

Note: How do I prevent a class from being allocated via the 'new' operator? (I'd like to ensure my RAII class is always allocated on the stack.) asks how to avoid the free store, but that's only half the answer. I guess(?) for containers, one can do something similar with the placement-new operator, but what's with arrays and the data-member case?

The restriction should be compiler-enforced. (No runtime checking.)

By example:

class User {
  ...
  StackOnly m; // may be OK (depending if below implementable)
};

void f() {
  StackOnly obj; //ok
  StackOnly arrobj[42]; // fail!
  StackOnly* pObj = new StackOnly(); // fail!
  std::vector<StackOnly> v; // fail!

  User obj; //ok
  User arrobj[42]; // fail!
  User* pObj = new StackOnly(); // fail!
  std::vector<User> v; // fail!
}
Community
  • 1
  • 1
Martin Ba
  • 37,187
  • 33
  • 183
  • 337
  • 6
    It sounds like [XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem), why do you need this? – Algirdas Preidžius Nov 22 '16 at 22:13
  • By "prevent", do you want to enforce at compile time or run time? – Kevin Le - Khnle Nov 22 '16 at 22:15
  • @AlgirdasPreidžius - Academic interest. A class can be restricted *from* the heap, or *to* the heap (as evidenced in linked question): what more can I do. – Martin Ba Nov 22 '16 at 22:15
  • What if the stack was dynamically allocated to a thread? The primary stack of the process can be identified by a characteristic address range, but it will be harder for threads. – Kaz Nov 22 '16 at 22:15
  • @Kaz - the stack is the stack. I don't care where the memory comes from, but only that it behaves like the stack (automatic storage dureation, etc.) – Martin Ba Nov 22 '16 at 22:17
  • 2
    Why is should `StackOnly arrobj[42];` fail? `arrobj` is an object with automatic storage duration. – NathanOliver Nov 22 '16 at 22:24
  • @MartinBa Well, the answer you linked to, doesn't, actually, tell how to disable allocation on the heap. Since even if you `delete` all the `new`, and `delete` methods in the class definition, you can still call `new` from the global namespace like so `A* a = ::new A;` (same for `delete`: `::delete a;`). – Algirdas Preidžius Nov 22 '16 at 22:24
  • @AlgirdasPreidžius - good observation. I missed that. Still doesn't invalidate this question here I guess. – Martin Ba Nov 22 '16 at 22:29

2 Answers2

4

The following class allows only: X&& x = X::MakeInstance(); or const X& x = X::MakeInstance(); usages:

class X
{
public:
    X(const X&) = delete;
    X(X&&) = delete;
    X& operator =(const X&) = delete;
    X& operator =(X&&) = delete;

public:
    static X MakeInstance() { return {}; }

    static void* operator new     (size_t) = delete;
    static void* operator new[]   (size_t) = delete;
    static void  operator delete  (void*)  = delete;
    static void  operator delete[](void*)  = delete;

private:
    X() = default;
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Looks good to me. Haven't tried it yet. We note: [The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.](http://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary) – Martin Ba Nov 23 '16 at 08:35
1

What we can do is some nonportable hack whereby we obtain the stack pointer like this:

volatile char var, *approximate_stack_ptr = &var;

We do this inside the constructor for the object.

Then we can check that the address of the object itself (the this pointer) is within, say, 4096 bytes of approximate_stack_pointer; i.e.

// formally undefined behavior here, but chances are good
// it will work with many compilers:

ptrdiff_t delta = (char *) this - approximate_stack_ptr;

// Then, check that delta is between -4096 and 4096

The 4096 value is generous because it can be; the actual delta will likely be a lot smaller. There is no need to make it strict, though. An object that is not defined on the stack in any valid way will be significantly far away from the stack pointer; much farther than 4096 bytes. (Unless we are dealing with a very tiny embedded system implementation where 4096 is like half the addressable space.)

Kaz
  • 55,781
  • 9
  • 100
  • 149
  • just cast both pointers to `intptr_t` before doing the subtraction, then you have an unspecified result which may be non-portable, but it won't ever be undefined behavior. – Ben Voigt Nov 22 '16 at 22:23
  • @BenVoigt Good suggestion. – Kaz Nov 22 '16 at 22:24
  • This is a runtime check. I'm more interested in whether the compiler can restrict it (or rather, how much can be restrictied with the compiler.) – Martin Ba Nov 22 '16 at 22:24
  • 2
    There's nothing in C++ that discriminates between automatic and dynamic scoping, in the context of a class's constructor. – Sam Varshavchik Nov 22 '16 at 22:27