First, a little about OOP and how C ++ and other languages, such as Java, differ.
People tend to use object-oriented programming for several different purposes:
General programming: creating common code; that is, it works on any object or data that provides the specified interface without worrying about implementation details.
Modularity and encapsulation: prevention of too strong connection of different parts of the code with each other (the so-called "modularity"), hiding irrelevant implementation details from its users.
This is another way to think about sharing issues.
Static polymorphism: the default setting to implement any behavior for a particular class of objects while maintaining modular code when a set of possible settings is already known when writing your program.
(Note: if you do not need to keep the code modular, the choice of behavior will be as simple as if or switch , but then the source code will have to take into account all the possibilities.)
Dynamic polymorphism: like static polymorphism, with the exception of the many possible settings, it is not yet known - perhaps because you expect the library user to be able to implement specific behavior later, for example. to create a plugin for your program.
In Java, the same tools (inheritance and redefinition) are used to solve almost all of these problems.
The good thing is that there is only one way to solve all the problems, so it is easier to learn.
The disadvantage is sometimes - but not always negligible: a solution that solves problem No. 4 is more expensive than a solution that needs to be resolved only for # 3.
Now enter C ++.
C ++ has different tools to solve all these problems, and even when they use the same tool (e.g. inheritance) for the same problem, they are used in different ways, that they are actually completely different solutions than The classic "inherit + override" you see in Java:
For this, common programs are created: C ++ template . They are similar to Java generators, but in fact Java generics often require inheritance to be useful, while C ++ templates have nothing to do with inheritance at all.
Modularity and encapsulation: C ++ classes have public and private access modifiers, as in Java. In this regard, the two languages are very similar.
Static polymorphism: Java has no way to solve this particular problem, and instead you are forced to use the solution for # 4, paying a fine that you don't have to pay. On the other hand, C ++ uses a combination of template class es and CRTP inheritance to solve this problem. This type of inheritance is very different from the type for # 4.
Dynamic polymorphism: C ++ and Java allow overriding inheritance and function and are similar in this regard.
Now back to your question. How can I solve this problem?
From the discussion above, it follows that inheritance is not the only hammer designed for all nails.
Probably the best way (although perhaps the most difficult way) is to use # 3 for this task.
If necessary, you can implement # 4 on top of it for classes that need it without affecting other classes.
You declare a class called Shape and define the basic functionality:
class Graphics; // Assume already declared template<class Derived = void> class Shape; // Declare the shape class template<> class Shape<> // Specialize Shape<void> as base functionality { Color _color; public: // Data and functionality for all shapes goes here // if it does NOT depend on the particular shape Color color() const { return this->_color; } void color(Color value) { this->_color = value; } };
Then you define the overall functionality:
template<class Derived> class Shape : public Shape<>
Once you do this, you can define new shapes:
template<> class Square : public Shape<Square> { Point _top_left, _bottom_right; public: size_t vertices() const { return 4; } Point vertex(size_t vertex_index) const { switch (vertex_index) { case 0: return this->_top_left; case 1: return Point(this->_bottom_right.x, this->_top_left.y); case 2: return this->_bottom_right; case 3: return Point(this->_top_left.x, this->_bottom_right.y); default: throw std::out_of_range("invalid vertex"); } }
This is probably the best method, since you most likely already know all the possible shapes at compile time (i.e. you do not expect the user to write a plugin to define his own form), and therefore is not needed any deal with virtual . However, it keeps the code modular and separates the problems of different forms, effectively providing you with the same advantages as the dynamic polymorphism approach.
(This is also the most efficient option at runtime, at the cost of a bit more complicated at compile time.)
Hope this helps.