Как базовый класс C ++ может определить во время выполнения, был ли метод переопределен? - PullRequest
3 голосов
/ 26 ноября 2009

Пример метода ниже предназначен для определения того, был ли он переопределен в производном классе. Ошибка, которую я получаю от MSVC, подразумевает, что просто неправильно пытаться получить указатель функции на «связанный» член, но я не вижу логической причины, почему это должно быть проблемой (в конце концов, это будет в this -> * 1002 виртуальные таблицы *). Есть ли какой-нибудь хакерский способ исправить этот код?

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”;
    }
};

Ответы [ 3 ]

4 голосов
/ 26 ноября 2009

Нет способа определить, был ли метод переопределен, за исключением чисто виртуальных методов: они должны быть переопределены и быть не чистыми в производном классе. (В противном случае вы не можете создать экземпляр объекта, так как тип все еще "абстрактный".)

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

Вы можете предоставить определение чисто виртуального метода, если производные классы могут или должны вызывать его:

void A::f() {}

За ваш комментарий: «Если бы метод не был переопределен, это означало бы, что вместо этого можно попытаться сопоставить вызов с другим методом».

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();
  }
};

Теперь любой производный класс может обеспечивать свое собственное поведение, или наследие будет использоваться как запасной вариант, если они этого не делают. Виртуальный do_method является закрытым, потому что производные классы не должны вызывать его. (NonLegacy может сделать его защищенным или общедоступным в зависимости от ситуации, но по умолчанию рекомендуется использовать тот же уровень доступности, что и у его базового класса.)

1 голос
/ 28 апреля 2014

Вы действительно можете это выяснить. Мы столкнулись с той же проблемой, и мы нашли способ сделать это.

#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;
    }
};

У нас есть два класса A и B, а B использует A в качестве базового класса.

Следующие функции могут использоваться для проверки того, что B что-то переопределило в 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));
}

int n - это номер, данный методу, поскольку они хранятся в vtable. Обычно это порядок, в котором вы определяете методы.

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;
}

Вывод будет

Function 0 is overridden

EDIT: Мы получаем указатели на vtables для каждого экземпляра класса, а затем сравниваем указатель на методы. Всякий раз, когда функция переопределяется, для суперобъекта будет другое значение.

0 голосов
/ 26 ноября 2009

Нет портативного способа сделать это. Если вы намереваетесь иметь метод, который не является чисто виртуальным, но требует переопределения для каждого класса, к которому он будет вызываться, вы можете просто вставить оператор assert( false ) в реализацию метода базового класса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...