@Ch1156,
I mean you would think having different guns would call for inheritance
|
One of the problems in both teaching and early study is that most examples use trivial ideas to illustrate both inheritance and virtual functions, without clear engineering as to why or, as importantly, why
not.
Even before inheritance is taught, what I see in classroom study examples looks contrived just to illustrate how to make a class or struct, without much regard as to reason. The typical student example of a customer record class, for example, is unlikely to be found in real world, professional work. Database design (usually from SQL or related engines) changes on a whim, so it makes more sense to use containers which accept whatever a query from the database provides, not a fixed set of "fields" in a struct.
You may not regard what you're making as a class assignment, but I sense you are at a crossroads between student example and a more real project, where these design considerations evolve into something more like products and professional work.
Consider what happens with such weaponry when a game engine is used. In one popular engine I'm familiar with an indie game developer would create the artwork, fashion nodes representing the weapon's model, instantiate them as the player(s) acquire them (where the player is likely represented by a class). In that workflow, the weapon isn't really a class from which various definitions arise. It is a generic game node representing the position in the world upon which all manner of definitions from physics to special visual effects are applied to impact behavior. From a C++ viewpoint, this would look like a "node" class, with a transform (position and attitude), which then uses a simple container (perhaps a set or a map, could be a vector) to hold all of the "attributes" which flesh out what the weapon is. This container holds the attachment which define physics behavior, special visual effects, sounds, and...user code. This is not the layout of a virtual object as found in C++, but of a basic transform with attributes and behaviors attached to it.
One of the central notions in OOP is the selection of method by type. Overloaded functions are an elementary example:
1 2
|
double f( const double & );
int f( const int & );
|
Selection by "
blind " type implies, perhaps, a virtual function, where code must access a generic interface, and manipulate that interface without knowledge of the specific type. This is illustrated in class examples with that typical "shape/circle/rectangle" family (with shape being the virtual base). Yet, you'd never find an actual product doing that (say CAD or Blender).
I fear what schools use as early learning examples tend to leave students with the impression that those are fleshed out strategies, but they are "toy" examples that don't fit the real world.
One example of a bad fit is in rendering engines. I've seen a number of rendering engines implement the rendering object as a virtual object. The virtual base is a render interface, and the derived class implements the OpenGL/DirectX/Vulkan/Metal API selected for whatever platform is running.
The problem is that the user does not really need to switch between these API's at runtime, so the virtual base design of the render interface is inappropriate. It may be "nice" to switch from OpenGL and DirectX when on Windows, or between OpenGL and Metal on Apple, but why would the user want to do that at runtime? Every virtual function has a minor penalty.
It would be better to implement by some other means which does not involved virtual functions, like a policy based template design, or simple "PIMPL" pattern where the implementation is selected at compile time.
So, when you have seriously well grounded reasons,
fundamental reasons you can't avoid, which imply virtual base classes, use them. Otherwise, your thought may be a hint that it's not the only solution, and may not be the best solution.
Fitting attributes to a non-virtual object may be more efficient. Attaching objects representing behaviors may work better.
In games, in particular, you may discover you have several non-virtual base class designs, each with containers of attachments. Some of these may be collections of sound effects, visual effects, physics effects, and some of those might be virtual object designs. It is doubtful one needs a virtual base for a sound effect, for example, but of physics effects or special visual effects it is more likely.
From another perspective, contemplate what it takes to script or "mod" a game. What if the user were able to fashion new types of weapons? That couldn't be supported where each new type is a C++ class. That has to be implemented with runtime selectable attributes.