Tech/LxEngine/Internal Documentation/Code Quality
From Athile
Contents |
Code Quality
The architecture is based heavily on reference counting for memory management. This simplifies many questions of "object ownership", as objects in the system are by default shared and disposed of on the last use. This does not mean that memory management is no longer a concern.
- By default, use std::shared_ptr<>
- Be aware of how std::weak_ptr<> works and when to use it
- Use bare pointers only when needed
shared_from_this() creates a shared pointer from the this pointer (useful when a method needs to pass a shared pointer of itself to a method). Don't call reset() (that will create a second ref counted version). Don't call during the destructor.
Performance Notes
Note: the std shared pointer and weak pointer implementations are good because they are (1) standard and (2) well-tested. However, in future revisions of LxEngine, it may make sense to introduce a custom reference counting scheme since it may be slightly more efficient (i.e. the reason Boost has an intrusive_ptr<> template as well as the shared and weak ptrs). This should not be done until there are benchmarks to confirm the before/after performance.
Note: benchmarking shows that for small objects the overhead of std::shared_ptr<> is quite high and an intrusive implementation is notably faster.
Refactor for Correct Scope
Give object well-defined scopes. Does the object exist for the entire duration of the Document? Or the Session? Or across Sessions for the duration of the Installation?
If, for example, the object lasts the duration of the Document - but is too heavyweight to keep around when not in use, then hide that behind a wrapper object that does last for the duration of the Document. Then page / recreate the internals as necessary when it gets used.
Scopes
- Per-Network
- Per-Machine
- Per-User
- Per-Session
- Per-Document
- Per-Element
Hide caching whenever possible
Caching is useful in many places, but whenever possible make it transparent to the caller whether this is a new object, a shared object, and whether it is cached or not. Reference counting and copy-on-write are useful for this.
Lazy Evaluation with Intelligent Background Pre-caching
Don't do anything until it needs to be done (i.e. initializing subsystems, etc.). Use idle cycles to prepare for things that are likely to be done (where ideally there is some control over what "likely" means).
- Use Proxy Design Pattern
Open Issues
- std::shared_ptr<>'s shared_from_this() doesn't work in the destructor (for obvious reasons: there's no remaining references if the object is being destroyed), which means in some circumstances a raw pointer is needed instead of the shared ptr. This is inconsistent in the design: when is a shared pointer used and when is a smart pointer to be used? Is there a way to ensure smart pointers can always be used?