Force all classes to implement / override a 'pure virtual' method in multi-level inheritance hierarchy
Asked Answered
M

5

7

In C++ why the pure virtual method mandates its compulsory overriding only to its immediate children (for object creation), but not to the grand children and so on ?

struct B {
  virtual void foo () = 0;
};
struct D : B {
  virtual void foo () { ... };
};

struct DD : D {
  // ok! ... if 'B::foo' is not overridden; it will use 'D::foo' implicitly
};

I don't see any big deal in leaving this feature out.
For example, at language design point of view, it could have been possible that, struct DD is allowed to use D::foo only if it has some explicit statement like using D::foo;. Otherwise it has to override foo compulsory.

Is there any practical way of having this effect in C++?

Maraud answered 28/2, 2012 at 6:45 Comment(6)
You could factor the foo bit out into a separate class and demand that every class inherit directly from that one (privately).Edifice
It does not mandate anything of the sort (unless I am misreading). It mandates that any object that is instantiated must be of a class that does not have any undefined virtual methods.Remark
@iammilind: Sorry I still not sure what you mean. There is nothing special about immediate children as compare to other descendants. Maybe an example of why you think grandchildren are different.Remark
@LokiAstari, D needs to override B::foo() to be able to create an object of type D, however the same is not true for DD. DD object can still be instantiated, even though it doesn't override foo() (implicitly it uses D::foo()). My question is why is that ?Maraud
@iammilind: OK. But this has nothing to do with children Vs other descendants. It just forces one of the ancestors of DD to define foo() (if you are instantiating it) not the direct child of B.Remark
@LokiAstari, I know it. And that's the question. Why it's not mandated to all subsequent derived classes who want to instantiate objects?Maraud
M
8

I found one mechanism, where at least we are prompted to announce the overridden method explicitly. It's not the perfect way though.

Suppose, we have few pure virtual methods in the base class B:

class B {
  virtual void foo () = 0;
  virtual void bar (int) = 0;
};

Among them, suppose we want only foo() to be overridden by the whole hierarchy. For simplicity, we have to have a virtual base class, which contains that particular method. It has a template constructor, which just accepts the type same as that method.

class Register_foo {
  virtual void foo () = 0; // declare here
  template<typename T>  // this matches the signature of 'foo'
  Register_foo (void (T::*)()) {}
};
class B : public virtual Register_foo {  // <---- virtual inheritance
  virtual void bar (int) = 0;
  Base () : Register_foo(&Base::foo) {}  // <--- explicitly pass the function name
};

Every subsequent child class in the hierarchy would have to register a foo inside its every constructor explicitly. e.g.:

struct D : B {
  D () : Register_foo(&D::foo) {}
  virtual void foo () {};
};

This registration mechanism has nothing to do with the business logic. Though, the child class can choose to register using its own foo or its parent's foo or even some similar syntax method, but at least that is announced explicitly.

Maraud answered 12/3, 2012 at 4:0 Comment(0)
T
2

In your example, you have not declared D::foo pure; that is why it does not need to be overridden. If you want to require that it be overridden again, then declare it pure.

If you want to be able to instantiate D, but force any further derived classes to override foo, then you can't. However, you could derive yet another class from D that redeclares it pure, and then classes derived from that must override it again.

Toandfro answered 28/2, 2012 at 7:7 Comment(0)
B
2

What you're basically asking for is to require that the most derived class implement the functiom. And my question is: why? About the only time I can imagine this to be relevant is a function like clone() or another(), which returns a new instance of the same type. And that's what you really want to enforce, that the new instance has the same type; even there, where the function is actually implemented is irrelevant. And you can enforce that:

class Base
{
    virtual Base* doClone() const = 0;
public:
    Base* clone() const
    {
        Base* results = doClone();
        assert( typeid(*results) == typeid(*this) );
        return results;
    }
}

(In practice, I've never found people forgetting to override clone to be a real problem, so I've never bothered with something like the above. It's a generally useful technique, however, anytime you want to enforce post-conditions.)

Blackamoor answered 28/2, 2012 at 8:52 Comment(2)
The problem is that the part after the last child, that has implemented clone() will not be clone()d after calling clone() on a base class pointer - so some data would be silently skipped. At least, this typeid check is a good solution.Lacylad
@Lacylad The only problem is that it is run time, and not compile time. Better than nothing, I suppose. But as I said, I've never found this to be a real problem in practice.Blackamoor
M
1

A pure virtual means that to be instantiated, the pure virtual must be overridden in some descendant of the class that declares the pure virtual function. That can be in the class being instantiated or any intermediate class between the base that declares the pure virtual, and the one being instantiated.

It's still possible, however, to have intermediate classes that derive from one with a pure virtual without overriding that pure virtual. Like the class that declares the pure virtual, those classes can only be used as based classes; you can't create instances of those classes, only of classes that derive from them, in which every pure virtual has been implemented.

As far as requiring that a descendant override a virtual, even if an intermediate class has already done so, the answer is no, C++ doesn't provide anything that's at least intended to do that. It almost seems like you might be able to hack something together using multiple (probably virtual) inheritance so the implementation in the intermediate class would be present but attempting to use it would be ambiguous, but I haven't thought that through enough to be sure how (or if) it would work -- and even if it did, it would only do its trick when trying to call the function in question, not just instantiate an object.

Marginal answered 28/2, 2012 at 7:18 Comment(1)
The real question is why you would want to do this. It seems to impose unnecessary constraints on the implementation of the derived classes, which is undesirable; the constraints should concern only pre- and post-conditions and class invariants.Blackamoor
W
1

Is there any practical way of having this effect in C++?

No, and for good reason. Imagine maintenance in a large project if this were part of the standard. Some base class or intermediate base class needs to add some public interface, an abstract interface. Now, every single child and grandchild thereof would need to changed and recompiled (even if it were as simple as adding using D::foo() as you suggested), you probably see where this is heading, hells kitchen.

If you really want to enforce implementation you can force implementation of some other pure virtual in the child class(s). This can also be done using the CRTP pattern as well.

Windom answered 9/3, 2012 at 21:1 Comment(3)
My question is not about existing projects. If you are writing a base clasd and you want to enforce this to its hierarchy.Maraud
Then using the CRTP pattern you can do this. See my above edit.Windom
@Maraud you do realize that unless you only plan on writing code once and never having it modified, it will at some point be an "existing project".Barbabra

© 2022 - 2024 — McMap. All rights reserved.