How to force use of a static member on derived classes?

I have a Primitive base class from which I get several other classes - Sphere , Plane , etc.

Primitive applies some functions, for example intersect() , in its subclasses through pure virtual functions. The intersect calculation depends on the instance data, so it makes sense to use it as a member method.

My problem arises in the following: I want every derived instance to be able to identify its type, say, using the member method std::string type() . Since all instances of the same class return the same type, it makes sense to make the type() a static method. Since I also want every Primitive subclass to implement this method, I would also like to make it a pure virtual function like intersect() .

However, in C ++, static virtual methods are not allowed. C ++ static virtual elements? as well as can we create a virtual static method? (C ++) to ask similar questions, but they do not include the requirement to force a function to be executed on derived classes.

Can someone help me with the above?

+4
source share
4 answers

You may have a non-static virtual method that calls a static (or returns a static string) that is properly implemented in each derived class.

 #include <iostream> #include <string> struct IShape { virtual const std::string& type() const =0; }; struct Square : virtual public IShape { virtual const std::string& type() const { return type_; } static std::string type_; }; std::string Square::type_("square"); int main() { IShape* shape = new Square; std::cout << shape->type() << "\n"; } 

Note that you will have to implement the type() method for each subclass anyway, so itโ€™s best to do this so that the string is static. However, you can use enum instead of a string; avoid unnecessary string mappings in your code.

Now, returning to the basics of the problem, I think the design is somewhat flawed. You cannot have a common intersection function that works on all kinds of shapes, because the types of shapes that result from intersections are very different even for the same type (two planes can intersect in planes, lines, or not intersect in everything, for example ) Thus, in an attempt to provide a common solution, you will find yourself performing these kinds of checks everywhere, and this will grow unmaintainably the more forms you add.

0
source

Think about it for a second. I'm sure you have not only 2 basements, so let's summarize this.

The first things that come to mind are code duplication, extensibility, and proximity. . Expand them:

If you want to add a few more classes, you should change the code as little as possible.

Since the intersect operation is commutative , the code for intersecting A and B must be in the same place as the code for intersecting B and A , so the logic inside the classes themselves is out of the question.

In addition, adding a new class should not mean that you need to modify existing classes, but rather extend the delegate class (yes, we enter the templates here).

This is your current structure, I guess (or a similar, probably return type for intersect , but not important yet):

 struct Primitive { virtual void intersect(Primitive* other) = 0; }; struct Sphere : Primitive { virtual void intersect(Primitive* other) }; struct Plane : Primitive { virtual void intersect(Primitive* other); }; 

We have already decided that we donโ€™t need the intersection logic inside Plane or Sphere , so we are creating a new class :

 struct Intersect { static void intersect(const Sphere&, const Plane&); //this again with the parameters inversed, which just takes this static void intersect(const Sphere&, const Sphere&); static void intersect(const Plane&, const Plane&); }; 

This is the class in which you will add new functions and new logic. For example, if you decide to add the Line class, you simply add intersec(const Line&,...) methods intersec(const Line&,...) .

Remember that when adding a new class, we do not want to change the existing code. Therefore, we cannot check the type inside your intersection functions.

We can create a behavior class for this (strategy template) that behaves differently depending on the type, and we can continue it:

 struct IntersectBehavior { Primitive* object; virtual void doIntersect(Primitive* other) = 0; }; struct SphereIntersectBehavior : IntersectBehavior { virtual void doIntersect(Primitive* other) { //we already know object is a Sphere Sphere& obj1 = (Sphere&)*object; if ( dynamic_cast<Sphere*>(other) ) return Intersect::intersect(obj1, (Sphere&) *other); if ( dynamic_cast<Plane*>(other) ) return Intersect::intersect(obj1, (Plane&) *other); //finally, if no conditions were met, call intersect on other return other->intersect(object); } }; 

And in our original methods we will have:

 struct Sphere : Primitive { virtual void intersect(Primitive* other) { SphereIntersectBehavior intersectBehavior; return intersectBehavior.doIntersect(other); } }; 

An even cleaner design will be implemented by the factory in order to abstract the actual types of behavior:

 struct Sphere : Primitive { virtual void intersect(Primitive* other) { IntersectBehavior* intersectBehavior = BehaviorFactory::getBehavior(this); return intersectBehavior.doIntersect(other); } }; 

and you donโ€™t even need intersect be virtual, because it just does it for each class.

If you follow this design

  • no need to modify existing code when adding new classes
  • have implementations in one place
  • only IntersectBehavior propagated for each new type
  • provide intersect class implementations for new types

And I am sure that this could be further improved.

+4
source

For the reasons that they discussed in the link above, you cannot make a virtual element static.

Your question about requiring a forced implementation of a function on derived classes is handled by using the pure virtual function in an abstract base class that will ensure that these derived classes execute the function.

+2
source

Since all instances of the same class return the same type, it makes sense to make type () a static method.

No no. You use a static method when you do not need an instance of an object to call a function. In this case, you are trying to determine the type of object, so you need an instance.

All method bodies are still shared by all objects, so there is no need to worry about duplication. The only exception is when the functions are built-in, but the compiler will do everything possible to minimize the overhead and can turn it into a non-interface if the cost is too high.

PS Requiring a class to identify itself outside the class hierarchy is usually a bad code smell. Try to find another way.

+1
source

Source: https://habr.com/ru/post/1411335/


All Articles