I have a C++ class for which I only ever want it to be instantiated on the stack. I am using an api to access content that was developed in another (interpreted) language which comes with its own garbage collection. The mechanisms in this language know enough to leave any content that it finds references to on the stack alone, and since this native class contains such a reference, it is of vital importance that for correct behavior, the user of the native C++ class it does not ever try to allocate an instance of it anywhere else.

Note, I not only want to prohibit the instance of my class from being allocated with new (if that were all I needed to do, I could overload the class's new operator and make it private, or explicitly delete it since C++11), but to also disallow any static or possible global instances of the class as well. The only valid way to instantiate this class safely should be on the stack, and I would like to somehow guarantee that. As far as I know, making new private or deleting it also does not prevent another class from being declared with my class as a member variable and an instance of that being allocated on the heap.

How I am managing this right now is to have the word "Local" as part of the name of the class as a friendly reminder to the user that the instance is only intended to be used on the stack, but of course, this isn't actually enforced by the compiler or any other mechanism, and I would prefer a solution that is more enforceable.

Ideally I want to ensure this at compile time and fail compilation if used incorrectly. If this is simply not possible, throwing an exception at runtime when the instance is constructed is still an acceptable fallback. Solutions that work in C++11 or C++14 are fine.

Please note that this question is definitely NOT the same as this one, which only wanted to prevent allocaton with new

share|improve this question
1  
Can you go just one level deeper and have every element contain a reference to a singleton object on the stack? – Mark Ransom 7 hours ago
10  
Such a thing is not possible. – Kerrek SB 6 hours ago
2  
@markt1964 After reading your text deeper and seeing you mention this, I honestly don't see how this is possible regardless. That simply isn't how this language works. Right now it appears honor is about all you have to fallback to. – WhozCraig 6 hours ago
2  
Just like with people wanting singleton types when all they need is "a single object", I would recommend the conservative approach and say, "just put your objects on the stack and move on". – Kerrek SB 6 hours ago
4  
Even if you somehow manage to enforce that only automatic storage duration objects are created, the C++ implementation isn't obligated to put them on the stack and keep them there for their whole lifetime. It might, say, keep it in registers, only spilling to stack when required, or it might determine that nothing in your C++ code needs the value any longer and reuse the storage early, not aware of the GC behind its back. This is the sort of thing that needs to be worked out properly, with whatever API the GC supplies, rather than hacked on in the C++ side. – T.C. 5 hours ago

Disclaimer: 'stack' is not part of the c++ standard to my knowledge, there we have ASDVs (automatic storage duration variables). ABI might define stack. Note that sometimes these are passed in registers, which I believe is OK in your case.

Define a CPS (continuation-passing style) factory method:

class A {
public:
   template<typename F, typename... Args>
   static auto cps_make(F f, Args&&... args) {
      return f(A(std::forward<Args>(args)...));
   }
private:
   A(/* ... */) {}
   A(const A&) = delete;
   A(A&&) = delete;
};

Usage: pass a lambda taking A and the ctor parameters of A. E.g.

return A::cps_make([&](A a) {
   /* do something with a */
   return true;
});

Function arguments are always ASDVs inside.

How the code works: cps_make takes a functor (usually a lambda) which takes an instance of the given type; and optional ctor parameters. It creates the instance (by forwarding any optional params to the ctor), calls the functor and returns what the functor returns. Since the functor can be a lambda in C++11, it doesn't break the normal code flow.

The beauty of CPS is, you can have static polymorphism just by using an auto-lambda in C++14: your cps_make() can create just about anything you wish (hierarchy, variant, any, etc.). Then you save the virtual overhead for closed hierarchies. You can even have a lambda for normal flow and one if ctor would fail; this comes handy when exceptions are no-go.

The drawback is, currently you can't directly use the control flow statements of the outside scope inside the lambda. /* Hint: we're working on it. */

share|improve this answer
8  
May I kindly ask downvoters to explain why you think this is a wrong answer? Then I can learn... – lorro 6 hours ago
1  
That's quite clever. The down votes are probably from people who don't understand what this does. It might help to explain in addition to showing the code. – Peter Ruderman 6 hours ago
2  
Downsides: 1) Not able to use A as member or base class. 2) Arguments to creating an A are after the function using A, which is a very strange order. 3) As a result, the main effect is that it's obfuscating the code. IMHO, documenting that "using A not on the stack creates Undefined Behavior" is more in line with the C++ approach. – Sjoerd 6 hours ago
1  
@Sjoerd : 1) Member: impossible indeed. If you could, you'd be able to create a static instance of the enclosing class, which is defying OP's intentions. 2) I've tried to post the simplest solution, not the most convenient (lib-ready) one. At home, I'd probably do an override on operator>> to feed the lambda. 3) Yes, this is difficult code. Wouldn't call it obfuscated though: CPS is there for some time. It's not for everyone and you should always weight the cost vs. benefit. Doc is not checked by compiler: if cost of error is high, use types, if low, doc it. – lorro 6 hours ago
1  
@lorro consider using operator->* instead. Like f->*pass_A(args...) does a f(A(args...)). operator->* is freely overridable, rarely used, and is the member function dereference operator; this is sort of like writing a member function on f. – Yakk 5 hours ago

A possibility is to allow only temporary variables (with extended life time), something like:

class A
{
private:
    A() = default;
    A(const A&) = delete;
    A(A&&) = delete;
    A& operator =(const A&) = delete;
    A& operator =(A&&) = delete;

public:
    static A Make() { return {}; } 
};

auto&& g = A::Make(); // possible :/

int main() {
    auto&& a = A::Make(); // possible

#if 0
    new A(); // error

    struct AB
    {
        A::B z; // error
    };
#endif
}
share|improve this answer
    
Until guaranteed elision hits and you can do new A(A::Make()) anyway – Barry 3 hours ago

Okay, so here is my take:

struct stack_marker
{
    thread_local static uint8_t* marker;
    uint8_t stk;

    stack_marker()
    {
        if (marker != nullptr)
        {
            throw std::runtime_error("second twice marker! don't do it");
        }
        marker = &stk;
    }
};

thread_local uint8_t* stack_marker::marker = nullptr;

void sort3(uint8_t* (&a)[3]); //sorts 3 pointers, see gist

class only_on_stack
{
    uint8_t place;
public:
    NO_INLINE only_on_stack(int x)
    {
        uint8_t a;

        if (!stack_marker::marker)
        {
            // not initialized yet, either forgot to put stack_marker in main
            // or we are running before main, which is static storage

            //throw std::runtime_error("only on stack object created in non-stack");
            std::cout << x << ": I'm NOT on stack\n";
            return;
        }

        uint8_t* ptrs[] = {
            stack_marker::marker,
            &place,
            &a
        };

        sort3(ptrs);

        if (ptrs[1] == &place) // place must be in the middle
        {
            std::cout << x << ": I'm on stack\n";
        }
        else
        {
            //throw std::runtime_error("only_on_stack object created in non-stack");
            std::cout << x << ": I'm NOT on stack\n";
        }
    }
};

only_on_stack static_storage(1);
thread_local only_on_stack tl_storage(4);

int NO_INLINE stuff()
{
    only_on_stack oos(2);
}

int main()
{
    stack_marker mrk;
    stuff();
    auto test = new only_on_stack(3);
    tl_storage; // access thread local to construct, or gcc omits it
}

Admittedly, my solution is not the cleanest of them all, but it allows you to keep using the regular local object syntax.

Basically, the trick is to put 2 additional objects on the stack, other than our object: one at the beginning of the thread, and one at the constructor. Therefore, one of the objects is created on the stack after our object and one of them before. With this information, we could just check the order of the addresses of these 3 objects. If the object is really on the stack, address of it should be in the middle.

However, C++ doesn't define the address order of objects in a function scope, therefore doing something like this:

int main()
{
    int a;
    int b;
    int c;
}

Does not guarantee that &b is in the middle of &a and &c.

To workaround this, we could keep a in the main function and move b and c in a different force non-inlined function:

void NO_INLINE foo()
{
    int b;
    int c;
}

int main()
{
    int a;
    foo();
}

In this case, since compiler cannot know the local variables of foo in main, &a > &b, &c or &a < &b, &c. By applying the same thing to c by moving it to another non-inlineable function, we could guarantee that &b is in the middle of &a and &c.

In my implementation, stuff function is the foo function and the function which we move c into is the constructor of only_on_stack.

Actual working implementation is here: https://gist.github.com/FatihBAKIR/dd125cf4f06cbf13bb4434f79e7f1d43

It should work whether the stack grows downwards or upwards and regardless of the object file type and hopefully the ABI, as long as the compiler doesn't somehow reorder the local variables of non-inline functions.

This was tested with -O3 on g++-6 on linux and latest clang on mac os x. It should work on MSVC, hopefully someone can test it.

Output from both is:

1: I'm NOT on stack
2: I'm on stack
3: I'm NOT on stack
4: I'm NOT on stack

Usage is basically, you put a stack_marker object at the beginning of every thread (main included) and call another not inlineable function and use it as your actual entry point.

share|improve this answer
    
It works on MSVC as well – Fatih BAKIR 5 hours ago
    
since compiler cannot know the local variables [...] What about inter-source optimisation? – Walter 5 hours ago

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.