web-dev-qa-db-ja.com

C ++仮想関数が派生クラスで再定義されているかどうかを検出する方法

簡単に:派生クラスのインスタンスを指す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でデフォルトの実装を実行するかを知りたいと思います。

  • これをライブラリのユーザーに対して透過的にしたいので、たとえば、ユーザーがどの関数が再定義されたかを指定する派生方程式のコンストラクターにいくつかのフラグを設定するソリューションを探していません。

  • 考えられる解決策の1つは、Equationの仮想関数のデフォルトの実装で、Equationクラスにプライベートフラグを設定することです。次に、Solverクラスのコンストラクターは、このフラグをクリアし、仮想関数を実行し、フラグ値をチェックして、Equationの実装が呼び出されたかどうかを確認できます。ただし、仮想関数を実行するたびにフラグを設定するだけでアルゴリズムの速度が大幅に低下するため、これは避けたいと思います(これらの仮想関数の実行はプログラムの実行時間に大きく影響し、デフォルトの実装は単純に定数)。

35
Chris Johnson

仮想関数のオーバーライドを移植可能にチェックすることはできません。

知識をSolverに持ち込む必要があります。できれば、実行時フラグではなくタイプを介してください。

1つの(タイプベースの)方法は、dynamic_castを介してインターフェースの有無をチェックすることです。

おそらくより良い方法は、solve関数のオーバーロードを提供することです。あなたの場合はSolverコンストラクターです。

問題のより具体的な説明を提供すれば、おそらくより良いアドバイスが得られるでしょう。これは、誰かが(1)問題Pを解決する必要がある、(2)Pの解決策として技術的アプローチXを想定している、(3)Xがそれをカットしないことを発見し、(4)方法を尋ねる典型的な状況を思い出させます。 XをPの漠然とした記述、またはいくつかの無関係な問題Qに対しても機能させる。元の問題Pの詳細は、Xよりもはるかに優れた解決策、およびPの解決とは無関係にXを機能させる問題を示唆することがよくあります。

19

今後の参考のために、GCCがこの拡張機能を提供していることがわかりました: http://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html これにより、メソッドがでオーバーライドされたかどうかを確認できます。

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

ICCはこの拡張機能を公式にサポートしており、clangのドキュメントには記載されていませんが、コードは機能し、警告なしにコンパイルすることもできます。

ただし、MSVCはこれをサポートしていないため、サポートされています。

また、実装が変更された別のバージョンのライブラリにリンクしている場合、別のライブラリのヘッダー(つまりインライン)で定義されているメソッドでは機能しないようです。標準を正しく解釈すると、これは未定義の動作(つまり、実装を変更する)ですが、実装が同じままである場合は、アドレスも一意である必要があります。したがって、インラインメソッドではそれを行わないでください。

12
John Smith

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からクラスを派生させ、それがイベントメソッドをオーバーライドする場合、イベントを自動的に「リンク」します。

7
jK.

これはおそらくどういうわけか可能ですが、私はそれをしないことをお勧めします。あなたは:

  • OOP原則に違反します。一般的なクラス/インターフェースが与えられている場合は、実装の詳細を確認しないでください。これにより、クラス間の依存関係が高まり、正反対になります。 OOPは何のために設計されたのか。
  • コードの複製。 Equationクラスによって返される定数を使用することを考慮します。
  • 難読化コード。型チェックを伴う多くの条件により、プログラムが見苦しくなります。
  • おそらく時期尚早の最適化を行っています。仮想関数呼び出しの実行と条件の実行の間に速度の違いはほとんどありません。プロファイラーを実行して、ボトルネックとなっているのが仮想関数であるかどうかを確認しましたか?適切に設計されたアプリケーション/ライブラリでは、ほとんどそうなりません。
4
Karel Petranek

それはあなたがしようとしている方法で行うことはできません。基本クラスの関数が、実装する仮想関数がオーバーライドされているかどうかを、明示的に通知されない限り知る方法はありません。

製図板に戻る必要があるようです。私があなたのデザインの問題をどのように解決するかは正確にはわかりませんが、あなたが今試みていることは単にうまくいかないことを私は知っています。

2
Edward Strange

また、仮想メソッドを使用しなかった場合はどうなりますか?

この時点で、私はテンプレートベースのソリューションを考えています。テンプレートを使用して、特定のクラスに特定のシグネチャを持つメソッドがあるかどうかを検出することは(それほど簡単ではありませんが)可能です。識別されると、コンパイル時に、軽量スキームと重いスキームを切り替えることができます。

すべてのコードをテンプレート化することはお勧めしません。テンプレート化されたコードはソリューションのTemplate(デザインパターン)部分のみをカバーし、依存関係を削減するために通常の関数を使用して手間のかかる作業を行うことができます。 。

また、メソッドのシグネチャを変更しない限り、これはクライアントに対して完全に透過的である可能性があります。

2
Matthieu M.

そのような検出を行う方法はわかりませんが、代わりに静的ポリモーフィズムの使用を検討しましたか?方程式のすべての仮想メソッドがデフォルト値のテンプレート「ポリシー」に置き換えられている場合、最適化はコンパイル時に実行できます。

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

おそらくこれは役に立ちます。それだけでは元の質問に答えることはできませんが、基本クラス(ここでは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);
}
1
spraff

最初の使用時に基本クラス関数へのポインターを取得し、実際の関数と比較するのはどうですか?

   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);
   }
0
zzz777