Подтвердить что ты не робот

Способы определения того, была ли переопределена виртуальная функция С++ в производном классе

Вкратце: Из указателя базового класса С++, который указывает на экземпляр производного класса, как можно определить во время выполнения ли нечистую виртуальную функцию (с реализацией в базовый класс) был повторно реализован в производном классе?

Контекст: Я пишу библиотеку С++ для решения определенных классов математического уравнения. Библиотека предоставляет класс Equation с несколькими виртуальными функциями, которые пользователи библиотеки используют в качестве базового класса для конкретного уравнения, которое они хотят решить. Библиотека также предоставляет класс Solver, который принимает Equation * как параметр конструктора. Затем пользователь записывает код по строкам:

class MyEquation : public Equation
{ ... } // equation definition here

int main()
{
  MyEquation myEqn;
  Solver solver(&myEqn);
  solver.Solve();
}

Если некоторые комбинации виртуальных функций в Equation не переопределены в классе производных уравнений, некоторые вычислительно дорогие части алгоритма, выполняемые объектом Solver, могут быть опущены. Поэтому я хотел бы знать, в конструкторе Solver, какие функции были переопределены, и вместо этого будет выполняться реализация по умолчанию в Equation.

  • Я хотел бы сделать это прозрачным для пользователей библиотеки, поэтому я не ищу решение, в котором, например, пользователь устанавливает некоторые флаги в конструкторе своего производного уравнения, определяя, какие функции были переопределены.

  • Одним из возможных решений является реализация по умолчанию виртуальных функций в Equation для установки частного флага в классе Equation; конструктор класса Solver может затем очистить этот флаг, запустить виртуальную функцию и проверить значение флага, чтобы узнать, была ли реализована реализация в Equation. Я бы хотел этого избежать, потому что просто установка флага каждый раз, когда выполняется виртуальная функция, замедляет процесс алгоритма (выполнение этих виртуальных функций существенно влияет на время выполнения программы, а реализация по умолчанию просто возвращает константа).

4b9b3361

Ответ 1

Невозможно проверить возможность переопределения виртуальной функции.

Вам нужно передать знания в Solver, предпочтительно через тип, в отличие от флага времени выполнения.

Один (тип) способ - проверить наличие или отсутствие интерфейса через dynamic_cast.

Вероятно, лучший способ - обеспечить перегрузку функции решения, в вашем случае конструктор Solver.

Вероятно, лучше дать рекомендации, если вы предоставили более конкретное описание проблемы. Это напоминает типичную ситуацию, когда кому-то (1) нужно решить некоторую проблему P, (2) предполагает технический подход X как решение P, (3) обнаруживает, что X не разрезает его, и (4) спрашивает, как сделайте X работы для неопределенного описания P или даже для некоторой не связанной проблемы Q. Детали оригинальной проблемы P часто предлагают гораздо лучшее решение, чем X, и проблемы, связанные с тем, что работа X не имеет отношения к решению P.

Ответ 2

В будущем, оказывается, GCC предоставляет это расширение: http://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html, который позволяет проверить, был ли метод переопределен с помощью

(void*)(obj.*(&Interface::method)) != (void*)(&Interface::method)

ICC поддерживает это расширение официально, clang docs не упоминает об этом, но код работает и даже компилируется без предупреждения.

MSVC этого не поддерживает, так что вот что.

Кроме того, похоже, что он не работает с методами, определенными в заголовке (например, inline), в отдельной библиотеке, если вы ссылаетесь на другую версию библиотеки, где была изменена реализация. Если я правильно интерпретирую стандарт, это поведение undefined (изменение реализации), но если реализация остается прежней, то адрес должен быть уникальным. Поэтому не делайте этого с помощью встроенных методов.

Ответ 3

После того, как вы только нашли результаты, ссылающиеся на расширение gcc PMF, я подумал, что должен быть правильный способ сделать это.

Я нашел решение без каких-либо хаков и, по крайней мере, проверено на работу с gcc и llvm:

#include <iostream>
#include <typeinfo>

struct A { virtual void Foo() {} };
struct B : public A { void Foo() {} };
struct C : public A { };

int main() {
    std::cout << int(typeid(&A::Foo) == typeid(&A::Foo)) << std::endl;
    std::cout << int(typeid(&A::Foo) == typeid(&B::Foo)) << std::endl;
    std::cout << int(typeid(&A::Foo) == typeid(&C::Foo)) << std::endl;
    return 0;
}

http://ideone.com/xcQOT6

PS: Я действительно использую его в системе CEventClient. Таким образом, вы получаете свой класс из CEventClient, и если он переопределяет метод события, он автоматически "связывает" событие.

Ответ 4

Даже если это возможно, возможно, я бы посоветовал не делать этого. Вы:

  • Нарушение принципов ООП. Если вам предоставлен общий класс/интерфейс, вы не должны проверять информацию о реализации. Это увеличивает зависимость между классами и в точности противоположно тому, для чего был создан ООП.
  • Дублирующий код. Учитывая, что вы используете константы, которые в противном случае возвращались бы классом Equation.
  • Obfuscating code. Многие условия с проверкой типов заставят вашу программу выглядеть уродливой.
  • Вероятно, преждевременная оптимизация. Между выполнением вызова виртуальной функции и условием практически нет разницы в скорости. Вы запустили свой профилировщик, чтобы проверить, являются ли это виртуальными функциями, которые являются узким местом? Это почти никогда не используется в хорошо спроектированном приложении/библиотеке.

Ответ 5

Это невозможно сделать так, как вы пытаетесь. Нет способа, чтобы функции базового класса знали, была ли переопределена виртуальная функция, не будучи явно упомянутой.

Кажется, вам нужно вернуться к чертежной доске. Не совсем точно, как бы я решил вашу проблему дизайна, но то, что вы пытаетесь сейчас, я знаю, просто не сработает.

Ответ 6

А что, если вы не использовали виртуальные методы?

В этот момент я думаю о шаблоне-базовом решении. Возможно (хотя и не так просто) использовать шаблоны для определения того, имеет ли данный класс метод с определенной сигнатурой или нет. После идентификации вы можете переключать во время компиляции между облегченной схемой и тяжелой схемой.

Обратите внимание, что я не предлагаю, чтобы все шаблоны были заархивированы, шаблонный код будет охватывать только часть Template (Design Pattern), а тяжелая атлетика может выполняться с регулярными функциями для сокращения зависимостей.

Кроме того, это может быть полностью прозрачным для клиентов, поскольку вы не изменяете сигнатуру методов.

Ответ 7

Я не знаю, как это сделать, но рассмотрели ли вы использование статического полиморфизма? Оптимизация может выполняться во время компиляции, если каждый виртуальный метод уравнения заменяется шаблоном "политика" со значением по умолчанию.

//default implementation of a virtual method turned into a functor
//which optionally takes a reference to equation
class DefaultFunctor1
{
public:
    //...
    double operator()(double x) { return log(x); }
};
template<class F1 = DefaultFunctor1>
class Equation
{
public:
    typedef F1 Functor1;
    //...
private:
    F1 f1;
};
class Solver
{
public:
    //....
    template<class Equation>
    void solve(Equation& eq)
    {
        Loki::Int2Type<
                        Loki::IsSameType<Equation::Functor1, DefaultFunctor1>::value
                       > selector;
        //choose at compile time appropriate implementation among overloads of doSolve
        doSolve(selector);
    }
private:
    //.... overloads of doSolve here
};
int main()
{
    Equation<> eq;
    Solver solver;
    solver.solve(eq); //calls optimized version
    Equation<MyFunctor> my_eq;
    solver.solve(my_eq); //calls generic version
    return 0;
}

Ответ 8

Возможно, это помогает. Он сам по себе не отвечает на ваш первоначальный вопрос, но вы можете увеличить свой базовый класс (здесь, Foo), чтобы использовать определенный интерфейс, если он поставляется или использовать метод по умолчанию.

#include <iostream>

struct X {
    virtual void x () = 0;
};

struct Y {
    virtual void y () = 0;
};


struct Foo {
    virtual ~ Foo () {}

    bool does_x () {
        return NULL != dynamic_cast <X*> (this);
    }

    bool does_y () {
        return NULL != dynamic_cast <Y*> (this);
    }

    void do_x () {
        dynamic_cast <X&> (*this) .x ();
    }

    void do_y () {
        dynamic_cast <Y&> (*this) .y ();
    }
};

struct foo_x : public Foo, public X {
    void x () {
        std :: cout << __PRETTY_FUNCTION__ << std :: endl;
    }
};


struct foo_y : public Foo, public Y {
    void y () {
        std :: cout << __PRETTY_FUNCTION__ << std :: endl;
    }
};

struct foo_xy : public Foo, public X, public Y {
    void x () {
        std :: cout << __PRETTY_FUNCTION__ << std :: endl;
    }

    void y () {
        std :: cout << __PRETTY_FUNCTION__ << std :: endl;
    }
};

void test (Foo & f)
{
    std :: cout << &f << " "
        << "{"
        << "X:" << f .does_x () << ", "
        << "Y:" << f .does_y ()
        << "}\n";

    if (f .does_x ())
        f .do_x ();

    if (f .does_y ())
        f .do_y ();
}

int main ()
{
    foo_x x;
    foo_y y;
    foo_xy xy;
    test (x);
    test (y);
    test (xy);
}

Ответ 9

Как получить указатель на функцию базового класса при первом использовании и сравнить его с фактическим

   class Base { virtual int foo(); }
   class Derived : public Base { virtual int foo(); }

   bool Xxx::checkOverride()
   {
       int (Base::*fpA)();
       int (Base::*fpb)();

       fpA = &Base::foo;
       fpB = &Derived::foo;

       return (fpA != fpB);
   }