How can a C++ base class determine at runtime if a method has been overridden?
Asked Answered
P

3

6

The sample method below is intended to detect whether or not it has been overridden in a derived class. The error I get from MSVC implies that it is simply wrong to try to get the function pointer to a "bound" member, but I see no logical reason why this should be a problem (after all, it will be in this->vtable). Is there any non-hacky way of fixing this code?

class MyClass
{
public:
    typedef void (MyClass::*MethodPtr)();  

    virtual void Method()
    {
        MethodPtr a = &MyClass::Method; // legal
        MethodPtr b = &Method;  // <<< error C2276: ‘&’ : illegal operation on bound member function expression

        if (a == b)     // this method has not been overridden?
            throw “Not overridden”;
    }
};
Pya answered 26/11, 2009 at 6:50 Comment(0)
O
3

There is no way to determine if a method has been overridden, except for pure virtual methods: they must be overridden and non-pure in a derived class. (Otherwise you can't instantiate an object, as the type is still "abstract".)

struct A {
  virtual ~A() {} // abstract bases should have a virtual dtor
  virtual void f() = 0; // must be overridden
}

You can still provide a definition of the pure virtual method, if derived classes may or must call it:

void A::f() {}

Per your comment, "If the method had not been overridden it would mean it is safe to try mapping the call to the other method instead."

struct Base {
  void method() {
    do_method();
  }

private:
  virtual void do_method() {
    call_legacy_method_instead();
  }
};

struct Legacy : Base {
};

struct NonLegacy : Base {
private:
  virtual void do_method() {
    my_own_thing();
  }
};

Now, any derived class may provide their own behavior, or the legacy will be used as a fallback if they don't. The do_method virtual is private because derived classes must not call it. (NonLegacy may make it protected or public as appropriate, but defaulting to the same accessibility as its base class is a good idea.)

Ovotestis answered 26/11, 2009 at 6:52 Comment(1)
unfortunately there are many intermediate classes between the base class and the classes which may implement either new or old method. I was hoping to use this as a way to change as little code as possible, but that is looking less likely now.Pya
J
1

You can actually find this out. We encountered the same problem and we found a hack to do this.

#include<iostream>
#include<cstdio>
#include<stdint.h>

using namespace std;

class A {
public:
    virtual void hi(int i) {}
    virtual void an(int i) {}
};

class B : public A {
public:
    void hi(int i) {
        cout << i << " Hello World!" << endl;
    }
};

We have two classes A and B and B uses A as base class.

The following functions can be used to test if the B has overridden something in A

int function_address(void *obj, int n) {
    int *vptr = *(int **)&obj;
    uintptr_t vtbl = (uintptr_t)*vptr;

    // It should be 8 for 64-bit, 4 for 32-bit 
    for (int i=0; i<n; i++) vtbl+=8;

    uintptr_t p = (uintptr_t) vtbl;
    return *reinterpret_cast<int*>(p);
}

bool overridden(void *base, void* super, int n) {
    return (function_address(super, n) != function_address(base, n));
}

The int n is the number given to method as they are stored in vtable. Generally, it's the order you define the methods.

int main() {
    A *a = new A();
    A *b = new B();

    for (int i=0; i<2; i++) {
        if (overridden(a, b, i)) {
            cout << "Function " << i << " is overridden" << endl;
        }
    }

    return 0;
}

The output will be

Function 0 is overridden

EDIT: We get the pointers to vtables for each class instance and then compare the pointer to the methods. Whenever a function is overridden, there will be a different value for super object.

Jacobah answered 28/4, 2014 at 11:56 Comment(2)
I was very excited to see this solution, but i get an "access violation" during "return *reinterpret_cast<int*>(p);" Anybody had the same issue?Maltase
That reinterpret_cast violates TBAA. It also doesn't make a ton of sense since what is wanted here is an integer conversion, not a pointer conversion.Northway
T
0

There's no portable way of doing that. If your intention is to have a method that is not pure virtual, but needs to be overridden for every class it will be called on you can just insert an assert( false ) statement into the base class method implementation.

Tinkle answered 26/11, 2009 at 6:55 Comment(5)
Why would you have a non-virtual method that derived classes must override? And if it is virtual but not pure, what advantage does this have over making it pure instead?Ovotestis
In some cases you might need several kinds of derived classes. For classes belonging to a certain kind only a subset of methods will be called. That certainly sounds like inperfect OO design but you sometimes need this in real life.Tinkle
My question comes from a need to support a mapping between two different interface methods, one new and one legacy. If the method had not been overridden it would mean it is safe to try mapping the call to the other method instead. Some objects implement new method and others don't, and similarly some calling code would call new and some legacy (messy I know, part of gradual refactoring). Without this ability it can only be mapped in one direction.Pya
So use non-pure virtual methods in wrapping interface which call the legacy ones. If you override it and still need the legacy-call, explicitly call the base method in the override.Zima
Had we used non-virtual wrapping methods from the start it would have been a piece of cake, but unfortunately these methods are overridden at several locations in a rather large class hierarchy.Pya

© 2022 - 2024 — McMap. All rights reserved.