@Xanadu4ever,
On the subject of inheritance as a model for frameworks waning over the years:
The early implementation of C++ did not have templates, but it did have virtual functions. In C it was possible to call functions via pointers to functions, and the virtual function put that tendency on steroids.
If you've used any GUI framework you're probably familiar with the implementation of a user interface to a framework using virtual base classes. To create a window or control, the user inherits from one of the framework's classes. This provides an interface in the base to the user's class, and can impose requirements upon the user through pure virtual functions.
When templates did not exist, this was also the way containers and reference counted pointers were implemented. In these frameworks the user would inherit their classes from an "object" base class (it was just common to use the name object in some form). This is known as an intrusive pointer or intrusive node (like the nodes of a tree or linked list).
When templates were first introduced most of us writing in C++ set about making our own smart pointers. Using templates this meant the target classes did not have much of any requirement placed upon them (didn't require inheritance from "object"). This was immediately extended by a personal library of containers. Many were awful, flawed, but useful. This was before the STL was released (which was among the better, if not the best, of these kinds of project implied by the very existence of templates).
Eventually a number of interesting use cases of template design became design patterns. A book by Alexandrescu from 2001 demonstrated policy based designs. It is loosely related to the "curiously repeating template pattern" (CRTP) pattern. Policies look like this:
template < typename T > class A : public T {};
The key here is that the class from which this derives it the parameter to the template. This can impose base class interface and behavior upon A, but it can mutate by user selection. In a way this returns the motivation for virtual functions, or intrusive base class components (like "object").
The CRTP pattern is related but differs in this way:
1 2 3
|
template <typename T> class B {}; // "T" may be the "receiver" of this design pattern
class D : public B<Derived> {}; // the base "knows" the derived type natively now
|
You can see the similarity, but now the base can call functions in the derived class directly. It is impractical to make a base class without templates like this (may even seem absurd). The point, here, is that "B" can mutate based on "D", but impose no virtual functions, giving a runtime performance benefit. These two methods perform a compile time "morphism" selected by the consumer of the object(s).
These are but a few paradigms that have emerged over the years which provide varied ways of implementing the kind of interface to a framework, or class, which have different performance or compliance features upon the user's code.
Going back to reference counted pointers, the old style, inheriting from "object", put the "knowledge" of reference counting in the base of the object. This meant that a single allocation created one instance with both the reference counting logic (and data), but also included the user's code and data in one object.
The first smart pointers using templates, which did not require the user class to inherit from any base and basically imposed on particular requirements on the class, couldn't do that. The initial formations everyone created, included the original shared_ptr, put the "node" in it's own instance (this held the reference count and logic). The node "owned" the user's instance through a pointer. That was, therefore, two allocations for every user instance. One of the "node", the other for the user's instance that node controlled. In case this isn't already familiar, the smart pointer itself "pointed to" the node (usually as an instance on the stack or as a member of a class).
This was extended by the use of "in place new" and explicit call to the destructor, to create a "node" object which also provided storage for the user's instance in one allocation. You may know this as "make_shared", "make_unique" and "make_scoped".
Containers have similar transformations due to templates, but until C++11 there was a minor issue. For most containers the object had to be copyable. If not, you only way the user to store instances in the container was through a pointer (so the pointers could be copied instead of the instances). Adding "move semantics" to the language fixed that problem (you just have to move instead of copy).
I'm half through the max post limit, and my car's timing belt replacement beckons me to mechanic's duty, but if I've missed any particular highlight on this subject it may come from other posts or further exchange. You may be able to figure some out yourself, too.
(Geeze, I got into electronics and computer programming just so I didn't have to end up with grease from my fingers to my elbows, but I refuse to pay $2700 to someone else to do something I can complete for $200 in parts, especially for a 2004 car.)