检测是否已在派生类中重新定义C ++虚函数的方法

时间:2011-01-19 21:58:16

标签: c++ virtual-functions

简而言之:从指向派生类实例的C ++基类指针,如何在运行时确定是否为非纯虚函数(具有实现的实现)基类)是否已在派生类中重新实现?

上下文: 我正在编写一个C ++库来解决某些类的数学方程。该库提供了一个具有多个虚函数的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中的实现。我想避免这种情况,因为每次执行虚函数时简单地设置标志会使算法减慢很多(这些虚函数的执行对程序的运行时间有很大帮助,而默认实现只返回一个常数)。

9 个答案:

答案 0 :(得分:19)

您无法移植地检查虚拟功能的覆盖。

您需要将知识带到Solver,最好是通过类型而不是运行时标志。

一种(基于类型)方式是通过dynamic_cast检查接口的存在与否。

可能更好的方法是提供solve函数的重载,在你的情况下是Solver构造函数。

如果您提供更具体的问题描述,可能会给出更好的建议。它确实提醒了某人(1)需要解决某些问题P的典型情况,(2)设想技术方法X作为P的解决方案,(3)发现X不削减它,(4)询问如何使X适用于P的模糊描述,甚至是一些不相关的问题Q.原始问题P的细节通常会提出比X更好的解决方案,以及使X工作的问题,与解决P无关。

答案 1 :(得分:11)

为了将来参考,结果是GCC提供了这个扩展:http://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html,它允许检查方法是否被覆盖

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

ICC正式支持此扩展,clang的文档没有提及,但代码可以正常编译,甚至没有警告。

但是,MSVC并不支持这一点,所以就是这样。

此外,如果您链​​接到实现已更改的库的不同版本,它似乎无法使用单独库中的标头(即内联)中定义的方法。如果我正确地解释了标准,这是未定义的行为(改变实现),但如果实现保持不变,那么地址也应该是唯一的。所以不要使用内联方法。

答案 2 :(得分:8)

在找到链接到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派生您的类,如果它覆盖了一个事件方法,它将自动链接&#39;事件。

答案 3 :(得分:4)

尽管这可能在某种程度上可行,但我建议不要这样做。你是:

  • 违反OOP原则。如果您获得通用类/接口,则不应检查实现细节。这增加了类之间的依赖性,与OOP的设计完全相反。
  • 复制代码。考虑使用Equation类会返回的常量。
  • 模糊代码。许多带有类型检查的条件会让您的程序看起来很难看。
  • 可能进行过早优化。执行虚拟函数调用和条件之间几乎没有速度差异。您是否运行了探查器来检查它是否是作为瓶颈的虚拟功能?在设计良好的应用程序/库中几乎不会出现这种情况。

答案 4 :(得分:2)

不能按照你尝试的方式完成。基类的函数无法知道它们实现的虚函数是否被覆盖而没有被明确告知。

看起来你需要回到绘图板。不完全确定我如何解决你的设计问题,但你现在正在尝试的东西我知道根本无法解决。

答案 5 :(得分:2)

如果你没有使用虚拟方法怎么办?

此时我想到的是基于模板的解决方案。使用模板来检测给定类是否具有某个特定签名的方法是可能的(尽管不是那么容易)。一旦确定,您就可以在编译时切换轻量级方案和重型方案。

请注意,我并不建议所有代码都是模板化的,模板化代码只会覆盖解决方案的Template(设计模式)部分,而繁重的工作可以通过常规函数来完成关于依赖。

此外,如果您不改变方法的签名,这对客户来说可以完全透明。

答案 6 :(得分:2)

我不知道如何进行此类检测,但您是否考虑使用静态多态?如果使用默认值的模板“policy”替换Equation的每个虚拟方法,则可以在编译时完成优化。

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

答案 7 :(得分:1)

也许这会有所帮助。它本身并没有回答你的原始问题,但你可以扩充你的基类(这里,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);
}

答案 8 :(得分:0)

如何在第一次使用时获取指向基类函数的指针,并将其与实际的

进行比较
   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);
   }