As my home economics teacher always said, "it's not something you HAVE to do, it's something you GET to do."
In the beginning, I was scared of using heap memory because I was afraid of leaks. I knew I wasn't going to be able to keep track of stuff if the project got too big, this was before smart pointers. While watching chili's videos, he mentioned ownership and responsibility. This sort of gave me some confidence in at least having an idea how to manage memory allocations and deallocations. Then he taught polymorphism, and allocating memory was a must. Once I got the hang of it, I thought, "wow, I can just allocate everything and my stack will be almost empty", so allocated all class objects only leaving the built in types on the stack ( char, int, float, ... ). I hadn't started using references at that point, didn't understand them.
I started using references and liked them so much more for the same reason you do, don't have to use the indirection operator ( operator->). I stopped using them for a while, because they were a hassle to store or more accurately they made it a hassle to instantiate classes if stored. So I resigned myself to using references only for function parameters. This still holds true, mostly, today. If I don't have to move a class or will only instantiate one instance of the class I will store a reference to something. For instance, I will store a reference to a top level class within a nested class. I might have multiple instances of the nested class, but they never get overwritten or replaced since you can't reassign a reference.
Once smart pointers had been out for a while and I had a chance to read up on them ( actually I probably watched more cppcon videos on YouTube than read ) I decided to give them a shot. As you've seen from this thread, I don't have all the answers, Chili and a few others will point me in the correct direction when I'm off. My understanding of smart pointers is:
- prefer unique_ptr for all allocations to show ownership
- use shared_ptr when ownership of a resource is shared.
- pass the underlying raw pointer to functions unless you are transferring ownership.
From this standpoint, I hope you can see why I made such a fuss about storing raw pointers. It's been awhile since I've played with polymorphism especially on a project like yours, so I can't really say how I'd handle the situations you are dealing with.
Somewhere along the way, I started getting hooked on performance. I've always heard that using the stack was faster than using heap memory for a couple of reasons.
- Heap memory was expensive to allocate.
- It was less likely the data you want is less likely to be in cpu cache
Through my own experience, it's not the allocations that take the most time, it's the deallocations. It is true that it is less likely to have the data you want in CPU cache if it's stored on the heap, but this isn't always true. If you use the array overload of unique_ptr or a container such as vector, your objects are stored contiguous and cache coherency or data locality isn't much of an issue. Polymorphism still takes a hit in performance as each element is allocated in some random place in memory and not necessarily because of virtual functions. Because I became obsessed with performance, I stopped using polymorphism and preferred stack based allocation. There were other reason I stopped using polymorphism actually. One was because I kept trying to shoehorn it in. I could never seem to find an elegant way of using it and still maintain distinction between their true types when needed. Take for instance two game entities which one has a bounding rectangle and the other a bounding circle. Both are shapes, but have different methods of collision detection and correction. In order to determine the algorithms for collision and correction, you must know the shapes that are colliding. I've tried enums and I've tried runtime type information and neither feel right. I've looked into design patterns such as the "visitor" pattern, but there is too much coupling. Even the "observer" pattern requires polymorphism or some kind of way of determining types.
I believe design patters are the hardest thing for me to understand in programming. I think it's mostly because everyone has their own spin on how to define the pattern, which means I'm left coming up with my own implementation and I hate feeling my way through things.
Wow, this turned into so much more than intended. Good day and good luck.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com