2

クラスをインターフェイスの実装として使用したい場合がありますが、コードを変更したり、このクラスをカプセル化したりできません (派生など)。他の状況では、そのような状況を回避したい場合があります。// 次の例では、コードを変更できない次の「封印された」クラスがあると仮定します。

class my_sealed_class;
class my_lock 
{
    friend class my_sealed_class;
    my_lock() {}
    my_lock(my_lock const&) {}
};

class my_sealed_class : virtual my_lock
{
private:
    int x;

public:
    void set_x(int val) { x = val; }
    int  get_x() const  { return x; }
};

基本的に 2 種類のデザイン パターンから選択できます。 1.) インターフェイスのメンバー オブジェクトとしての実装。

template<class implementation>
class my_interface0
{
private:
    implementation m_impl;

public:
    void set_x(int val) { m_impl.set_x(val); }
    int  get_x() const  { return m_impl.get_x(); }
};

C++03 テンプレートの機能により、これはかなり型安全です。多くのコンパイラは、実際に呼び出された「実装」のメンバー関数をインライン化します - 残念ながら、すべてのコンパイラではありません。場合によっては、不要な呼び出しに対処する必要があります。

2.) ベースから派生せずに CRTP パターンを使用する。

template<class derived>
class my_interface1
{
public:
    void set_x(int val) { reinterpret_cast<derived*>(this)->set_x(val); }
    int  get_x() const  { return reinterpret_cast<derived const*>(this)->get_x(); }
};

ええ、私は知っています: それは CRTP パターンが使用される通常の方法ではありません。通常、次のように定義します。

template<class derived> class base { void foo() { static_cast<derived*>(this)->foo(); } };
class derived : base<derived>      { void foo() {} };

残念ながら、これはできません。実装クラスのコードは変更できないことに注意してください。私のバージョンの CRTP パターンで my_interface1 を使用して正確に何が起こっているか考えてみましょう。プログラミング言語 C++03 の理論上、派生クラス ポインターを基本クラスの 1 つにキャストしても安全です。一方、反対方向 (ベースから派生) にキャストしたり、まったく異なる型にキャストしたりすることは安全ではありません。メモリ オブジェクトが基本クラスと派生クラスに必要な数のバイトを予約しているという保証はありません。しかし、実際には、インターフェイスにはメンバーが含まれていないため、これはこの例には属しません。したがって、サイズは 0 バイトです (クラスが空の場合でも、演算子 new は少なくとも 1 バイトを割り当てることに注意してください)。この場合は' s - 実際には、任意の型ポインタを 'my_interface1 ポインタにキャストしても安全です。決定的な利点は、ほとんどすべてのコンパイラが実際に呼び出されたメンバー関数への呼び出しをインライン化することです。

int main()
{
    // Not even worth to mention: That's safe.
    my_sealed_class msc;
    msc.set_x(1);

    // Safe, because 'my_interface0' instantiates 'my_sealed_class'.
    my_interface0<my_sealed_class> mi0;
    mi0.set_x(2);

    // Absolutely unsafe, because 'my_interface1' will access memory which wasn't allocated by it.
    my_interface1<my_sealed_class> mi1;
    mi1.set_x(3);

    // Safe, because 'my_interface1*' references an my_sealed_class object.
    my_interface1<my_sealed_class>* pmi1 = reinterpret_cast<my_interface1<my_sealed_class>*>(&msc);
    pmi1->set_x(4);

    return 0;
}

では、ベストプラクティスは何だと思いますか? よろしくお願いします。

4

2 に答える 2

2

オプション2の動作は未定義であり、厳密なエイリアシングルールに違反しているように見えますこれにより、特定の最適化が無効になったり、コードが破損したりする可能性があります。さらに、コードを読んで入ってくる人は、ほぼ確実にその動作について非常に混乱するでしょう。

最初の例は有効なコードであり、ほとんどのプログラマーが理解できる標準パターンであり、ほとんどすべてのコンパイラーがインライン化する必要があります。私は間違いなくこのアプローチをお勧めします。コンパイラがパススルー呼び出しをインライン化できない奇妙な世界でも、プロファイリングによって余分な呼び出しが重大なボトルネックであることが示された場合にのみ、パフォーマンスを向上させるための別のアプローチを検討します。

編集:元の質問の最後の質問に答えるには:オプション1よりもオプション2を使用することはベストプラクティスではありません。オプション1でパフォーマンスの問題がある場合は、未定義の動作よりも解決するためのより良い方法があります。

于 2011-05-18T15:22:01.303 に答える
0

my_sealed_classしたがって、私が理解していれば、メソッドは仮想と宣言されておらず、変更できないため、継承(から派生)を使用できません。プライベート継承を試しましたか?

この場合、オプション (1) の合成を使用します。これは、よりシンプルで読みやすく、コンパイラーが最適化するのに適しているためです。また、STLが行うことだと思います。

(2)高速に見えるだけで、再解釈されたキャストは最適化が容易ではなく、コードが少し読みにくくなります。

于 2011-04-19T21:01:46.053 に答える