Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any way to enforce that instances are only ever on the stack?

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

like image 449
markt1964 Avatar asked Apr 19 '17 22:04

markt1964


2 Answers

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. */

like image 54
lorro Avatar answered Nov 12 '22 09:11

lorro


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.

like image 6
Fatih BAKIR Avatar answered Nov 12 '22 07:11

Fatih BAKIR