Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enforce a static member on derived classes?

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

Primitive enforces some functionality, e.g intersect(), on its subclasses through pure virtual functions. The computation of intersect depends on instance data, so it makes sense to have it as a member method.

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

However, static virtual methods are not allowed in C++. C++ static virtual members? and Can we have a virtual static method ? (c++) ask similar questions but they do not include the requirement of enforcing the function on derived classes.

Can anyone help me with the above?

like image 732
wsaleem Avatar asked May 08 '12 15:05

wsaleem


People also ask

How do you access base class members in a derived class?

A base class's private members are never accessible directly from a derived class, but can be accessed through calls to the public and protected members of the base class.

Can a derived class inherit members?

A derived class inherits member functions of base class. A derived class can be used anywhere the base class is expected. A derived class inherits member functions of base class. A derived class can be used anywhere the base class is expected.

Can derived classes access public members?

A class can always access its own (non-inherited) members. The public accesses the members of a class based on the access specifiers of the class it is accessing. A derived class accesses inherited members based on the access specifier inherited from the parent class.

Can derived classes access private data members?

Private members of the base class cannot be used by the derived class unless friend declarations within the base class explicitly grant access to them.


2 Answers

Let's think about this for a second. I'm sure you don't only have 2 sublcasses, so let's generalize this.

First things that come to mind are code duplication, extensibility and closeness. Let's expand on these:

Should you want to add more classes, you should change code in the least places possible.

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

Also, adding a new class shouldn't mean you have to modify existing classes, but rather extend a delegate class (yes, we're going into patterns here).

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

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 already decided we don't want the intersection logic inside Plane or Sphere, so we create 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 where you'll be adding the new functions, and the new logic. For example, if you decide to add a Line class, you just add the methods intersec(const Line&,...).

Remember, when adding a new class, we don't want to change existing code. So we can't check the type inside your intersect functions.

We can create a behavior class for this (strategy pattern), which behaves differently depending on type, and we can extend afterwards:

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'd have:

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

An even cleaner design would be implementing a factory, to abstract out the actual types of the behavior:

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

and you wouldn't even need intersect to be virtual, because it would just do this for every class.

If you follow this design

  • no need to modify existing code when adding new classes
  • have the implementations in a single place
  • extend only IntersectBehavior for each new type
  • provide implementations in the Intersect class for new types

And I bet this could be perfected even further.

like image 196
Luchian Grigore Avatar answered Sep 22 '22 02:09

Luchian Grigore


For the reasons they discussed in the link you provided, you cannot make a virtual member static.

Your question about the requirement of enforcing the function on derived classes is handled by making the function pure virtual in the abstract base class which will enforce that derived classes must implement the function.

like image 35
dag Avatar answered Sep 22 '22 02:09

dag