General design question; Is it better to dynamically load component 'pages' on the heap, or have them all loaded on stack

Hey there,

Hopefully this question isn’t too vague as I’m sure there are many cases where both are better.

In my situation, I have about 12 (and probably more later) different ‘pages’ that contain many components and sometimes subpages. Currently, all the pages are loaded on the stack at startup and just switched visible on/off when switching to a new page.

I’m wondering if it would be a better approach to have all the pages as Unique_ptrs and load/de-load the pages when switching. I assume that the non-visible pages, aren’t painting, but they are probably still receiving many other calls (resize, listeners, etc.)

Any thoughts appreciated.

As far as I can tell, this isn’t really a question about the heap and the stack, since all of your components are going to be stored on the heap either way. But the question of whether to pre-create all of your components or to create and destroy them on demand is a legitimate question.

For most cases, it seems to me that creating and destroying them on demand is the way to go and makes for cleaner code. (This is also how juce::TabbedComponent seems to work, which makes me think that it’s the right choice). The only real disadvantage to this approach is the time it takes to create a new Component, but in most cases this is pretty trivial, and it’s not going to happen very often anyway. So unless you find a special reason not to, I’d go with this option.

3 Likes

Not something I’ve ever done, but as an extension of this line of thought, you could chose to store the components with some kind of timer to delay the deletion of them. That way if things are shown/hidden frequently, the lifetime is extended enough to reuse the same resources, but once it has been hidden for a longer time, it is deleted.

3 Likes

I think you should really go with creation on demand. It’s most likely cleaner and easier to understand when reading your code. It is very obvious (even without a profiler) if this causes you performance problems. E.g. press the button and wait for a second to load the window change. If that happens, then you get to optimise the code. But keep in mind, that pre-creating might solve the performance issue but wasn’t the real issue. E.g. the create on demand algorithms uses/acquires very “hot” resources that are handled only by one thread during setup but by many threads during normal runtime. So the real problem isn’t the UI but the way it interacts with the rest of your code.

All sorts of crazy options here but don’t optimise something you don’t know causes any problems.

1 Like

This is really the point here, but I think that’s why you asked.

  • Creating a Component is only a heavy operation, if you put it on the desktop (i.e. a window). Otherwise it’s a few bytes and a repaint. The only thing to watch out is, if you do heavy stuff in your component’s constructor.

  • Creating and manipulating components is single threaded by design. All functions like repaint(), addAndMakeVisible() and setBounds() only work on the MessageManager / message thread. The Timer calls conveniently on the message thread too, but that means the presented Timer destruction wouldn’t help at all.

  • To elaborate what LiamG said, members don’t go on the stack, they go where the whole object went. If the owning object is on the stack, the member is created together with the object on the stack (the owner is by sizeof(member) bytes larger). But the member can be a pointer like a std::unique_ptr, that offloads the memory on the heap, in case you don’t know the size of the object at compile time (polymorphy) or if it is not known if it will exist or not (there are ways around though, but that’s beyond the scope here).

  • The GUI thread should be quick, but does not need to be realtime safe, so heap allocations are totally fine.

Now the actual answer:

  • Components that are not always needed should go in a std::unique_ptr or an OwnedArray / std::vector<std::unique_ptr> and created on the fly, unless there is something inside the component you wrote that dictates otherwise.

  • Components that are always there, i.e. the parent makes no sense without the child, then it is convenient to have it as plain member, no confusion with lifetime then.

TL;DR: don’t overthink it, 99% of the cases GUI object creation is not the actual problem.

1 Like

This is why, IMO, it’s better to use the terms “static memory” and “dynamic memory” to describe something that’s instantiated along with its owner, or instantiated later (e.g. int x; or std::unique_ptr<int> y;).

This SO post sums it up pretty well: c - Difference between static memory allocation and dynamic memory allocation - Stack Overflow

Agreed! Not only do I find this way easier to manage but I also find it fits better with the mental model of your application - if a UI element isn’t visible then it doesn’t really “exist” at that point.

In the past when I’ve tried to have everything statically allocated, I’ve run into issues with non-visible components doing weird stuff in the background - like getting listener callbacks about events and then trying to update their layout only to fail because their size is 0x0. You then go down the rabbit hole of having these components dynamically add/remove themselves as listeners to things which is just a nightmere to manage.

I find that terminology problematic, so I wouldn’t use it to explain class members.
static refers to allocated at compile time (like explained in the SO post you linked), which is not the case for class members. class members are constructed with the class, they increase the size of the class by the size of the member. There is nothing about compile time and nothing about static.

i’d say generally stack for ease of code readability, but sometimes heap for a number of reasons like:

  • stuff needs too much ram to always exist
  • it’s not clear how many components you need at init, so you make a vector of unique components
  • it’s not clear what kind of component you need at init
  • stuff is not always visible anyway
  • you wanna make a menu that can have infinite amounts of sub menus that are also that menu

i’m sure i have forgotten some. last time i talked about that with someone i think we found more usecases.

1 Like

I think we established that a class member is not the same as stack memory.

Members of a class are treated as a stack in that they are allocated in the order they’re decalred, and destroyed in the opposite order. So you can consider it a local stack of sorts. The exact implementation of how the memory is actually organised in RAM is rarely of any importance.

Even a program as simple as int main() { int x = 10; return x; } could be considered heap memory from the OS’s point of view. Which is why static/dynamic are more useful terms - although I do agree that “static” is confusing since it has another meaning in C++ so another word may be more useful in it’s place.

1 Like

I use pointers for things that are potentially resource-heavy, like a file browser. They’re unavoidable for dynamic structures, when the amount / type of components is unknown at the parent’s construction. For the rest, I don’t think it’s worth the hassle -components are not heavy per se, and invisible components are not painted anyway. For all other functions, you’d have to make sure they’re done when the component is created, and while it exists. For example at resizing -without pointers, you call setBounds on all subcomponents in the top one, and you know it will cascade down to the whole UI, everything will be at the right size when it’s made visible. Otherwise, you have to resize the components when they’re created, but also in the parent’s resized(), after checking for nullptr.

1 Like

I think the closest analogy is ‘lazy initialisation’. You don’t create/init a component until you need it.

I typically decouple my data and view models (in this case, the component itself). Instead of having multiple constructors for the different data configurations, I handle reconfiguring the component internally when the data model changes; this is usually faster than recreating a component every time the model changes. This also enables you to create ‘blank’ components that do nothing but wait for the events (see the threading comment below).

Heap vs Stack doesn’t come into play this high up the UI abstraction chain.

Component construction shouldn’t be slow; creating sub-components is fast enough not to be a problem doing it on the fly unless you’re relying on things like URL requests, loading large files etc. (which can be multi-threaded, with callbacks that execute on the message thread to create/notify the component when the data is ready).

You’re golden if you can create your components on the fly every time and not see a slowdown (on the slowest targetted hardware). URL stuff should always be threaded, as it’s always relatively non-deterministic.

Be advised that it’s trivial to indirectly trigger a resize, and repaint method call from within your constructor, try not to make too many assumptions about your pointer being valid because you initialise it in the constructor.

1 Like

I usually choose creating/destroying on the fly, otherwise the component exists and might perform a lot of operations that you aren’t aware of.

On the other hand, I do want to mention that showing and hiding components simply by using setVisible can result in some very simple code. For instance, dialogs that are shown as child components (not on desktop), can hide itself by simply calling setVisible(false). I have come across a lot of different approaches for dialogs and most of the time the dialog has to take a reference to the parent or a lambda that will allow the dialog to close itself.

Most of the time I choose whatever solution gives me the least amount of code because and I don’t worry about performance until I start noticing performance problems.

2 Likes