31

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

Community
  • 1
  • 1
markt1964
  • 2,638
  • 2
  • 22
  • 54
  • 2
    Can you go just one level deeper and have every element contain a reference to a singleton object on the stack? – Mark Ransom Apr 19 '17 at 22:09
  • What behavior do you want from placement new? Do you need to differentiate between heap and stack? The problem is that in practice the memory reserved for these places depends a lot, for example gcc's `-fsplit-stack` – Mikhail Apr 19 '17 at 22:10
  • 2
    Is it not sufficient to use the api for all content handled in the other language? The c++ class should use that api for allocation or cleanup of {other language's stuff}, making it irrelevant how the c++ class is destructed. – Kenny Ostrom Apr 19 '17 at 22:10
  • It is irrelevant to that class how the C++ class is destructed.... I just do not want it containing references to things that the language might have garbage collected. If instances of my native class are only ever on the stack, the language knows to leave any content they refer to alive until there are no more references to it inside of the stack bounds. – markt1964 Apr 19 '17 at 22:12
  • 12
    Such a thing is not possible. – Kerrek SB Apr 19 '17 at 22:13
  • @WhozCraig that should be disallowed... and is why simply overload `new` or deleting it would not really present a solution that is signfiicantly better than the honor system based one I am using right now by naming the class something that reminds the user of it that it is only for automatic storage. – markt1964 Apr 19 '17 at 22:14
  • 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 Apr 19 '17 at 22:16
  • 5
    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 Apr 19 '17 at 22:24
  • If the C++ class contains a reference to data from the other API, then presumably the API's garbage collector won't even try to release the data while an instance of the C++ class refers to the data, so this is really a moot issue. The C++ class instance could be on the stack or heap, it wouldn't matter as long as the instance manages the reference correctly so the API doesn't try to release the data prematurely while the class instance is still alive. – Remy Lebeau Apr 19 '17 at 22:25
  • `alloca` can be used to grab stack and you might be able to overload new/delete to use this, but you will be having some fun here to catch all the edge cases. Might be better to put your C++ stuff in its own executable and talk with the interpreted stuff via the OS. – Michael Dorgan Apr 19 '17 at 22:27
  • That presumption would be wrong.... the only data that it won't try to release is data that is referred to by something on the program's stack. There are ways of blocking the garbage collection entirely, but they are prohibitively expensive, and I wouldn't want to use it in a general case when an instance of the class is being allocated in automatic storage anyways and wouldn't be be necessary – markt1964 Apr 19 '17 at 22:28
  • maybe this an idea : use a private subtype in a class and return objects of this private type via a factory function. The user can use the object like any other object except can't use its typename. – engf-010 Apr 19 '17 at 22:29
  • 1
    @markt1964: how does the API know that a reference exists on the stack? How does it even know how many references there are to begin with? They are outside of the API, outside of its control, there is no possible way for it to gleam WHERE the references exist, only WHAT they are referring to. The data being referred to has to exist in memory somewhere, so either the data was allocated on the stack, or it was allocated on the heap. The garbage collection should be smart enough to know that and act accordingly. Where the references are allocated is irrelevant. – Remy Lebeau Apr 19 '17 at 22:32
  • I'm not 100% sure of the specifics, but my understanding is that when it sees something being assigned to within the interpreted language, it iterates over the current program's stack bounds to see if there are any other pointers to the same content, and if there are, it will conservatively choose to leave it alive. – markt1964 Apr 19 '17 at 22:34
  • @markt1964L that would be extremely invasive, not to mention unreliable. What if multiple threads refer to the same piece of data within the API? Is the API going to iterate the stacks of every thread in the process? No, that is wasteful, time consuming, error prone, non-portable, and just outright wrong. A proper gargage collector would simply keep track of a reference count associated with the data item, where each reference must increment the counter when assigned and decrement the counter when cleared. The GC would then release the data appropriately after its counter falls to 0. – Remy Lebeau Apr 19 '17 at 22:37
  • Well, as I said, I'm not 100% sure of the specifics. I would imagine reference counting is involved as well, but that only goes so far... when you have a container containing a container containing the first container, reference counts are meaningless. You have to go through storage and for every entry that refers to something that your system knows about, walk down that tree to ensure that everything pointed to stays alive. When you are done, anything that you knew about that wasn't marked alive can be freed. – markt1964 Apr 19 '17 at 22:41
  • "*Well, as I said, I'm not 100% sure of the specifics*" - then I suggest you find out the specifics, because it has a BIG IMPACT on how you write your code. What you describe is not how a reliable garbage collection based API should work. Especially if it can be used in different programing languages, then it definitely cant do what you describe. And the whole "container containing a container containing the first container" is easily addressed using *weak* referencing rather than *strong* referencing. Only strong references to an object manipulate its reference count, weak references do not. – Remy Lebeau Apr 19 '17 at 22:45
  • @RemyLebeau What they are describing is EXACTLY how MANY garbage-collectors work. Including Ruby, the Bohem GC, JavaScriptCore, Chakra (MS JS engine), SBCL, D, Mono (even SGen!) and Emacs. Every one of those is used in production. You have probably used software that contained at least one of them. – Demi Apr 19 '17 at 22:55
  • @RemyLebeau The only thing the GC needs is to be able to tell, “Do these bytes point to something I manage?”. If the answer is “yes”, then that object is live and must not be moved. If the answer is “no” then that pointer can be ignored. In most cases, the rate of false positives (objects marked live that really aren’t) is acceptably low. And trust me, the other APIs for dealing with GCd objects in C++ are worse (try having to register each and every variable (including temporaries!) that holds a GC pointer). – Demi Apr 19 '17 at 23:00
  • 7
    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. Apr 19 '17 at 23:18
  • @T.C. On top of that: As main() cannot be called directly, all local variables of main() can be stored in static storage as only one version will ever exist. I'm not aware of any compiler that does, although it might be useful for really large objects. – Sjoerd Apr 19 '17 at 23:29
  • @Demi: I can't fathom any GC-based system scanning someone else's stacks that it doesn't own/manage, looking for active references. But whatever. I don't use *any* GC-based apps at all, and I don't use any GC-based APIs in my own code. – Remy Lebeau Apr 19 '17 at 23:32
  • Well, it only needs to scan the stack to look for references to data that the other language's own storage system suggest could be garbage collected. If a reference is found on the stack, it makes the conservative judgement that it should be kept alive (for now). Since the system knew about that data already anyways, there's no chance that it could corrupt memory. Even *IF* there is content on the stack that coincidentally happens to look exactly like a pointer to data that the GC was already considering freeing, the worst that happens is that the data doesn't get freed right then and there. – markt1964 Apr 19 '17 at 23:48
  • Why you even want to have an object? Are you in need of preserve some kind of state between api calls? – Logman Apr 19 '17 at 23:52
  • @T.C. has a point. If native interop is genuinely supported by the api you're using I would expect functions to pin/unpin gc managed objects. Maybe someone could make a suggestion based on the api you're using, if you include the name. – Jorn Vernee Apr 20 '17 at 11:31
  • They exist, but they are expensive.... and I would not want them to be used on data that actually *was* on the stack and wasn't even needed. – markt1964 Apr 20 '17 at 13:19
  • Don't you think the api designers have thought of that? It may already be taken into account. – Jorn Vernee Apr 20 '17 at 14:02
  • I do not think so.... if I have my class instance on the stack with a reference to this language's data, even at best marking it as unavailable for garbage collection is going to be completely redundant. Given the fact that accessing this data is already quite burdensome when using native code, I do not want to do any unnecessary processing, if there is any possible way it can be avoided (and why I would prefer a compile-time solution) – markt1964 Apr 20 '17 at 17:06

3 Answers3

40

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

lorro
  • 10,687
  • 23
  • 36
  • @Jarod42 : sorry & thanks for catching. Typing using phone, couldn't run it :) fixed. – lorro Apr 19 '17 at 22:40
  • @Sjoerd : ASDV is automatic storage duration variable. Resolved this acronym and CPS in the answer. – lorro Apr 19 '17 at 22:41
  • 6
    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 Apr 19 '17 at 22:45
  • 10
    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 Apr 19 '17 at 22:50
  • 1
    This, IMO, is the only valid answer. Some Rust code uses a similar trick. – Demi Apr 19 '17 at 22:56
  • 12
    @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 Apr 19 '17 at 23:06
  • 2
    @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 - Adam Nevraumont Apr 19 '17 at 23:16
  • @Sjoerd : 1) Using as a base class is possible [with cps], but way more complicated than above. You basically put the above pattern in a CRTP base class, create a key token (struct) that has the factory as a friend, then allow descendant's ctors which take the key token to create instances normally. Or you just 'tunnel down' the key token created in cps_make / cps_factory. Now, this is complex for OP's request and is probably unneeded there; but, if you'd need a pattern/example for that, I can come up with it. – lorro Apr 19 '17 at 23:17
  • @Yakk: I'm ok with either operator, but Sjoerd brout up the (valid) concern that f should come after args... I.e., with op>> it's like pass_A(args...) >> [&](A a) { /* ... */ }; – lorro Apr 19 '17 at 23:20
  • @lorro No need to do that for me. I'll just look for a way to tell the 3rd party library that an object is in use and create a simple RAII wrapper for such a dont_gc() function. A sensible library will have such a function. – Sjoerd Apr 19 '17 at 23:20
  • That's really clever :) Care to explain what you mean about that "hint"? – Daniel Jour Apr 19 '17 at 23:38
  • 1
    @DanielJour : statement expressions proposal, parametric/named SEs, sneak peak: http://lorro.hu/cplusplus/statement_expressions.html but please note this is way too work-in-progress. Comments are welcome though. – lorro Apr 19 '17 at 23:43
  • @lorro and `make_A(args..)->*f` does match the idea that the method pointer "goes on the right". – Yakk - Adam Nevraumont Apr 20 '17 at 00:41
  • 2
    [Here](http://coliru.stacked-crooked.com/a/5209a02490e84dd6) is a live example using CRTP to reduce the boilerplate of an instance. It is missing making (most) things private to avoid bypassing how it is supposed to be used. You have permission to copy it into your answer, if you want it, and modify it. – Yakk - Adam Nevraumont Apr 20 '17 at 00:52
  • @Yakk: nice :). Btw you can check if public ctor is available via std::is_constructible – lorro Apr 20 '17 at 07:08
  • Question: who are "we"? – YSC Apr 20 '17 at 11:47
  • @YSC: if you want a single point of contact, that's me. However, I've received a lot of help from ISO C++ mailing list: the idea of standardizin (non-parametric) statement expressions, the original idea on for-break, the hints that for-break/else and throwaway continuations (and thus exceptions) are similar, the tips on what should be extended and rewritten; also, comments on the original (for-else) workaround from my collegues; lots of help I've received. And I'm still open to new ideas on this, so if you have the time, your comments are very welcome :). – lorro Apr 22 '17 at 15:34
6

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.

Fatih BAKIR
  • 4,569
  • 1
  • 21
  • 27
  • *since compiler cannot know the local variables [...]* What about inter-source optimisation? – Walter Apr 19 '17 at 23:52
  • @Walter, If you mean link time optimizations, I'd expect the linker to respect a `noinline` request. [here](http://gcc.1065356.n8.nabble.com/Preventing-link-time-optimization-from-inlining-tp899277p899502.html) it's somewhat confirmed by also adding a `noclone` attribute as well. – Fatih BAKIR Apr 20 '17 at 12:43
  • In practice this is probably fine for most cases. But it's certainly possible for stack variables in the same thread to be in non-contiguous memory, in which case you can't say anything about the order. E.g. signal handlers with alternate stacks. Or an atypical system where that's just normal. – ughoavgfhw Apr 21 '17 at 00:44
5

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 InnerA
    {
        A a; // error
    };
#endif
}

It will no longer be valid in C++17 with guaranteed copy elision.

Jarod42
  • 203,559
  • 14
  • 181
  • 302