2

次のコードがあるとします。

template <class Derived>
class Base {
public:
   virtual void foo_impl() = 0;
   void foo() {
      static_cast<Derived*>(this)->foo_impl(); //A
      (*static_cast<Derived*>(this)).foo_impl(); //B
   }
};

class Derived : public Base<Derived> {
private:
   void foo_impl() {
      bar();
   }
};

いくつかの質問:

ラインAは仮想関数呼び出しを生成しますか?私がインターネットで見つけることができるものの大部分はこの方法で物事を行うことを推奨していますが、Derivedへのポインタが実際にはDerived2型のオブジェクトを指している可能性があることを考えると、コンパイラが静的ディスパッチを実行する方法がわかりません。パブリック派生。

行Bは、前のポイントで提起した問題を修正しますか(該当する場合)?呼び出しがもはやポインター上になく、したがって*を使用していることを考えると、そうなるようです。仮想関数呼び出しを回避します。しかし、コンパイラが逆参照されたキャストを参照型として扱う場合でも、仮想関数呼び出しを生成する可能性があります...その場合、回避策は何ですか?

C ++ 11のfinalキーワードをfoo_impl()に追加すると、いずれかの(またはその他の関連する)場合のコンパイラの動作が変わりますか?

4

3 に答える 3

5

行 A は仮想関数呼び出しを生成しますか?

はいfoo_impl()仮想であり、それをDerivedオーバーライドします。foo_impl()inDerived明示的にとしてタグ付けされていませんがvirtual、基本クラスにあり、これで仮想関数にするのに十分です。

ライン B は、前のポイントで提起した問題を解決しますか (該当する場合)?

いいえ。呼び出しがポインターまたは参照のどちらで行われているかは問題ではありません。コンパイラーは、関数をから派生しfoo_impl()たクラスのインスタンスで呼び出しているのか、または の直接のインスタンスで呼び出しているのかを認識しません。したがって、呼び出しは vtable を介して実行されます。 DerivedDerived

私が何を意味するかを見るには:

#include <iostream>

using namespace std;

template <class Derived>
class Base {
public:
   virtual void foo_impl() = 0;
   void foo() {
      static_cast<Derived*>(this)->foo_impl();
      (*static_cast<Derived*>(this)).foo_impl();
   }
};

class Derived : public Base<Derived> {
public:
   void foo_impl() {
      cout << "Derived::foo_impl()" << endl;
   }
};

class MoreDerived : public Derived {
public:
   void foo_impl() {
      cout << "MoreDerived::foo_impl()" << endl;
   }
};

int main()
{
    MoreDerived d;
    d.foo(); // Will output "MoreDerived::foo_impl()" twice
}

ついに:

C++11 の final キーワードを追加してfoo_impl()、コンパイラがいずれか (またはその他の関連する) の場合にどのように動作するかを変更しますか?

理論的には、そうです。このfinalキーワードにより、 のサブクラスでその関数をオーバーライドできなくなりますDerived。したがって、 へfoo_impl()のポインタを介してへの関数呼び出しを実行するDerivedと、コンパイラは呼び出しを静的に解決できます。ただし、私の知る限り、コンパイラはC++ 標準によってそうする必要はありません。

結論

いずれにせよ、実際にやりたいことは、基本クラスで関数をまったく宣言しないことだと思います。foo_impl()これは通常、CRTP を使用する場合に当てはまります。さらに、のfunctionにアクセスする場合は、クラスBase<Derived>afriendを宣言する必要があります。それ以外の場合は、公開できます。DerivedDerivedprivatefoo_impl()foo_impl()

于 2013-02-05T16:54:26.337 に答える
3

CRTP の一般的なイディオムでは、ベースで純粋仮想関数を宣言する必要はありません。コメントの1つで言及したように、これは、コンパイラが派生型のメンバーの定義を強制しないことを意味します(base での使用がある場合、useを介して、派生型に存在する必要があります)タイプ)。foofoo_impl

私は一般的なイディオムに固執し、ベースで純粋仮想関数を定義しませんが、本当にそれを行う必要があると感じた場合は、追加の修飾を追加して動的ディスパッチを無効にすることができます:

template <class Derived>
class Base {
public:
   virtual void foo_impl() = 0;
   void foo() {
      static_cast<Derived*>(this)->Derived::foo_impl();
      //                           ^^^^^^^^^
   }
};

追加の修飾を使用すると、Derived::動的ディスパッチが無効になり、その呼び出しは静的に解決されDerived::foo_implます。これには通常の注意事項がすべて含まれていることに注意してください。仮想関数を持つクラスがあり、オブジェクトごとに仮想ポインターのコストを支払いますが、CRTP ベースでの使用のように、最も派生した型でその仮想関数をオーバーライドすることはできません。動的ディスパッチをブロックしています...

于 2013-02-05T17:41:55.967 に答える
1

行 A と行 B の余分な言い回しは、生成されたコードにはまったく影響しません。誰がこれを推奨しているのかはわかりませんが (私は見たことがありません)、実際に効果があるのは、関数が仮想でない場合だけです。と書くだけfoo_impl()で完了です。

コンパイラが派生型を認識している場合、仮想関数呼び出しを回避する手段あります。ベクトルのようなクラスに使用されるのを見てきました(ベクトルの通常、スパースなど、さまざまな実装がある場合):

template <typename T>
class Base
{
private:
    virtual T& getValue( int index ) = 0;
public:
    T& operator[]( int index ) { return getValue( index ); }
};

template <typename T>
class Derived : public Base<T>
{
private:
    virtual T& getValue( int index )
    {
        return operator[]( index );
    }
public:
    T& operator[]( index )
    {
        //  find element and return it.
    }
};

ここでの考え方は、通常は基本クラスへの参照のみを処理するというものですが[]、タイトなループで使用しているためパフォーマンスが問題になる場合はdynamic_cast、ループの前に派生クラスを参照し、派生クラスで使用できます[]

于 2013-02-05T16:55:50.053 に答える