簡単に:派生クラスのインスタンスを指す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
の実装が呼び出されたかどうかを確認できます。ただし、仮想関数を実行するたびにフラグを設定するだけでアルゴリズムの速度が大幅に低下するため、これは避けたいと思います(これらの仮想関数の実行はプログラムの実行時間に大きく影響し、デフォルトの実装は単純に定数)。
仮想関数のオーバーライドを移植可能にチェックすることはできません。
知識をSolver
に持ち込む必要があります。できれば、実行時フラグではなくタイプを介してください。
1つの(タイプベースの)方法は、dynamic_cast
を介してインターフェースの有無をチェックすることです。
おそらくより良い方法は、solve関数のオーバーロードを提供することです。あなたの場合はSolver
コンストラクターです。
問題のより具体的な説明を提供すれば、おそらくより良いアドバイスが得られるでしょう。これは、誰かが(1)問題Pを解決する必要がある、(2)Pの解決策として技術的アプローチXを想定している、(3)Xがそれをカットしないことを発見し、(4)方法を尋ねる典型的な状況を思い出させます。 XをPの漠然とした記述、またはいくつかの無関係な問題Qに対しても機能させる。元の問題Pの詳細は、Xよりもはるかに優れた解決策、およびPの解決とは無関係にXを機能させる問題を示唆することがよくあります。
今後の参考のために、GCCがこの拡張機能を提供していることがわかりました: http://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html これにより、メソッドがでオーバーライドされたかどうかを確認できます。
(void*)(obj.*(&Interface::method)) != (void*)(&Interface::method)
ICCはこの拡張機能を公式にサポートしており、clangのドキュメントには記載されていませんが、コードは機能し、警告なしにコンパイルすることもできます。
ただし、MSVCはこれをサポートしていないため、サポートされています。
また、実装が変更された別のバージョンのライブラリにリンクしている場合、別のライブラリのヘッダー(つまりインライン)で定義されているメソッドでは機能しないようです。標準を正しく解釈すると、これは未定義の動作(つまり、実装を変更する)ですが、実装が同じままである場合は、アドレスも一意である必要があります。したがって、インラインメソッドではそれを行わないでください。
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;
}
PS:私は実際にCEventClientシステムでそれを使用しています。したがって、CEventClientからクラスを派生させ、それがイベントメソッドをオーバーライドする場合、イベントを自動的に「リンク」します。
これはおそらくどういうわけか可能ですが、私はそれをしないことをお勧めします。あなたは:
それはあなたがしようとしている方法で行うことはできません。基本クラスの関数が、実装する仮想関数がオーバーライドされているかどうかを、明示的に通知されない限り知る方法はありません。
製図板に戻る必要があるようです。私があなたのデザインの問題をどのように解決するかは正確にはわかりませんが、あなたが今試みていることは単にうまくいかないことを私は知っています。
また、仮想メソッドを使用しなかった場合はどうなりますか?
この時点で、私はテンプレートベースのソリューションを考えています。テンプレートを使用して、特定のクラスに特定のシグネチャを持つメソッドがあるかどうかを検出することは(それほど簡単ではありませんが)可能です。識別されると、コンパイル時に、軽量スキームと重いスキームを切り替えることができます。
すべてのコードをテンプレート化することはお勧めしません。テンプレート化されたコードはソリューションのTemplate
(デザインパターン)部分のみをカバーし、依存関係を削減するために通常の関数を使用して手間のかかる作業を行うことができます。 。
また、メソッドのシグネチャを変更しない限り、これはクライアントに対して完全に透過的である可能性があります。
そのような検出を行う方法はわかりませんが、代わりに静的ポリモーフィズムの使用を検討しましたか?方程式のすべての仮想メソッドがデフォルト値のテンプレート「ポリシー」に置き換えられている場合、最適化はコンパイル時に実行できます。
//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;
}
おそらくこれは役に立ちます。それだけでは元の質問に答えることはできませんが、基本クラス(ここでは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);
}
最初の使用時に基本クラス関数へのポインターを取得し、実際の関数と比較するのはどうですか?
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);
}