Yes.
Swift made this mistake too. Classes are always heap allocated (and passed by reference and refcounted) and structs are always stack allocated (and passed by value).
It makes for a super awkward time trying to abstractly define a data model: you need to predict how people will be using your types, and how they’re used affects whether you should use struct or class.
The “swifty” way to do it is to just eschew class altogether and just always use struct, but with the enormous caveat that only classes can interop with ObjC. So you end up in an awkward state where if you need to send anything to an ObjC API, it has to be a class, and once you have a lot of classes, it starts to be more expensive to hold pointers to them inside structs, since it means you need to do a whole lot of incrementing/decrementing of refcounts. Because if you have a struct with 10 members that are all classes, you need to incur 10 refcount bumps to pass it to a function. Which means you may as well make that holding type a class, so that you only incur one refcount bump. But that just makes the problem worse, and you end up with code bases with all classes, even though that’s not the “swifty” way of doing things.
Rust did it right: there’s only one way to define a piece of data, and if you want it on the heap, you put it in a Box. If you want to share it, you put it in an Arc.
It always seemed weird to me. You need to know which data type you are working with when passing things around and can not adjust according to the application. For certain types from the standard library it makes sense, like datetimes you probably want always be copied. But when you work in a big team where everybody has their own style and special optimization corner case, it decays quickly.
There isn't. For this to work, functions for inline types have to be compiled such that one of these approaches is used:
1. They always take inline values by pointer, and we have to guarantee those pointers are never invalidated. This again means you need some sort of borrow checking scheme.
2. We compile two versions for each function: one that takes the data by value, and one by pointer, resulting in significant code bloat and compiler complexity.
I think the split also better captures the intent: heap types are for cyclic and heavily mutated values, inline types are more for short-lived mostly immutable values.
This is how C++ is designed. Unfortunately, it precludes types from taking dependencies on their own address, which is critical for e.g. atomics. As far as I know, there is no way to actually force a C++ class to be heap allocated. I’ve tried.
Newer languages like Swift give the type designer the ability to say “the address of this object in memory is important and must remain stable for the lifetime of this object.” This decays to heap allocation.
Make the destructor (and possibly the constructors) private, have public factory functions that hand out unique_ptr with a custom deleter, or define a public destroying operator delete (C++20) and use unique_ptr with its default deleter.
It does not! You just have to make it non-copyable and non-moveable. std::mutex and std::atomic do exactly this.
> As far as I know, there is no way to actually force a C++ class to be heap allocated. I’ve tried.
As others have already pointed out: private constructor + factory method. However, this pattern is typically used for different reasons (implementation hiding or runtime polymorphism) and heap allocation is only a side effect.
> This decays to heap allocation.
No! The object might just as well live on the stack or have static storage duration.
It's easier to reason about value semantics than reference semantics. If you want a class with a stable address that is heap allocated, then do so by writing class that has a private field that is heap allocated, preferably managed by a unique_ptr. Then disable copy assignment and construction and make a judgement call if you want to support move assignment, construction.
In effect, the idea is to make this aspect of the type an implementation detail that the user doesn't need to concern themselves with. They just create an instance of the type, and the type deals with the ownership or ensuring that the address is pinned, or whatever other details are needed to make it work. And of course the type does this by delegating that work to unique_ptr or some other means of ownership.
If anything, I’ve found it’s more useful when I want to bypass such wrapping to force a class to use some particular memory (like from a bulk allocation). The STL is pretty good, but there are warts that which still force the default heap allocator like std::function.
Seems trivial to box the inline types like most languages do.