4

次のコードを検討してください。

struct A
{
    virtual ~A() {}
    virtual int go() = 0;
};

struct B : public A { int go() { return 1; } };

struct C : public B { int go() { return 2; } };

int main()
{
    B b;
    B &b_ref = b;

    return b_ref.go();
}

GCC 4.4.1 ( を使用-O2) では、 への呼び出しB::go()がインライン化されます (つまり、仮想ディスパッチは発生しません)。これは、コンパイラが実際に型変数a_refを指していることを認識していることを意味します。参照を使用して を指すことができますBが、コンパイラはこれが当てはまらないことを予測できるほどスマートであるため、関数呼び出しを完全に最適化し、関数をインライン化します。BC

すごい!それは信じられないほどの最適化です。

しかし、では、GCC が次のケースで同じことをしないのはなぜでしょうか?

struct A
{
    virtual ~A() {}
    virtual int go() = 0;
};

struct B : public A { int go() { return 1; } };

struct C : public B { int go() { return 2; } };

int main()
{
    B b;
    A &b_ref = b;

    return b_ref.go(); // B::go() is not inlined here, and a virtual dispatch is issued
}

何か案は?他のコンパイラはどうですか?この種の最適化は一般的ですか? (私はこの種のコンパイラの洞察に非常に慣れていないので、興味があります)

2 番目のケースが機能する場合、次のような非常に優れたテンプレートを作成できます。

template <typename T>
class static_ptr_container
{
public:
    typedef T st_ptr_value_type;

    operator T *() { return &value; }
    operator const T *() const { return &value; }

    T *operator ->() { return &value; }
    const T *operator ->() const { return &value; }

    T *get() { return &value; }
    const T *get() const { return &value; }

private:
    T value;
};

template <typename T>
class static_ptr
{
public:
    typedef static_ptr_container<T> container_type;
    typedef T st_ptr_value_type;

    static_ptr() : container(NULL) {}
    static_ptr(container_type *c) : container(c) {}

    inline operator st_ptr_value_type *() { return container->get(); }
    inline st_ptr_value_type *operator ->() { return container->get(); }

private:
    container_type *container;
};

template <typename T>
class static_ptr<static_ptr_container<T>>
{
public:
    typedef static_ptr_container<T> container_type;
    typedef typename container_type::st_ptr_value_type st_ptr_value_type;

    static_ptr() : container(NULL) {}
    static_ptr(container_type *c) : container(c) {}

    inline operator st_ptr_value_type *()  { return container->get(); }
    inline st_ptr_value_type *operator ->()  { return container->get(); }

private:
    container_type *container;
};

template <typename T>
class static_ptr<const T>
{
public:
    typedef const static_ptr_container<T> container_type;
    typedef const T st_ptr_value_type;

    static_ptr() : container(NULL) {}
    static_ptr(container_type *c) : container(c) {}

    inline operator st_ptr_value_type *() { return container->get(); }
    inline st_ptr_value_type *operator ->() { return container->get(); }

private:
    container_type *container;
};

template <typename T>
class static_ptr<const static_ptr_container<T>>
{
public:
    typedef const static_ptr_container<T> container_type;
    typedef typename container_type::st_ptr_value_type st_ptr_value_type;

    static_ptr() : container(NULL) {}
    static_ptr(container_type *c) : container(c) {}

    inline operator st_ptr_value_type *() { return container->get(); }
    inline st_ptr_value_type *operator ->() { return container->get(); }

private:
    container_type *container;
};

これらのテンプレートは、多くの場合、仮想ディスパッチを回避するために使用できます。

// without static_ptr<>
void func(B &ref);

int main()
{
    B b;
    func(b); // since func() can't be inlined, there is no telling I'm not
             // gonna pass it a reference to a derivation of `B`

    return 0;
}

// with static_ptr<>
void func(static_ptr<B> ref);

int main()
{
    static_ptr_container<B> b;
    func(b); // here, func() could inline operator->() from static_ptr<> and
             // static_ptr_container<> and be dead-sure it's dealing with an object
             // `B`; in cases func() is really *only* meant for `B`, static_ptr<>
             // serves both as a compile-time restriction for that type (great!)
             // AND as a big runtime optimization if func() uses `B`'s
             // virtual methods a lot -- and even gets to explore inlining
             // when possible

    return 0;
}

それを実装することは実用的でしょうか?(そして、それがマイクロ最適化であると言い続けないでください..)

- 編集

static_ptr<>の問題は、私が公開した問題とは何の関係もないことに気付きました。ポインター型は保持されますが、インライン化されません。static_ptr_container<>::value が参照でもポインターでもないことを確認するために、GCCは必要なほど深くは行かないと思います。申し訳ありません。しかし、その質問はまだ答えられていません。

- 編集

static_ptr<>実際に動作するバージョンを作成しました。また、名前を少し変更しました。

template <typename T>
struct static_type_container
{
    // uncomment this constructor if you can't use C++0x
    template <typename ... CtorArgs>
    static_type_container(CtorArgs ... args)
            : value(std::forward<CtorArgs>(args)...) {}

    T value; // yes, it's that stupid.
};

struct A
{
    virtual ~A() {}
    virtual int go() = 0;
};

struct B : public A { int go() { return 1; } };

inline int func(static_type_container<Derived> *ptr)
{
    return ptr->value.go(); // B::go() gets inlined here, since
                            // static_type_container<Derived>::value
                            // is known to be always of type Derived
}

int main()
{
    static_type_container<Derived> d;
    return func(&d); // func() also gets inlined, resulting in main()
                     // that simply returns 1, as if it was a constant
}

唯一の弱点は、ユーザーがptr->value実際のオブジェクトを取得するためにアクセスする必要があることです。オーバーロードoperator ->()は GCC では機能しません。実際のオブジェクトへの参照を返すメソッドは、それがインラインの場合、最適化を中断します。お気の毒に..

4

1 に答える 1

2

これは明確な答えではありませんが、一部の人にとっては役立つかもしれないので、とにかく投稿するかもしれないと思いました.

コメンターのJulio Guerraは、 Static C++ Object-Oriented Programming (SCOOP)と呼ばれる C++ のイディオム (論文では「パラダイム」と呼んでいますが、それは少し多すぎると思います) を指摘しました。SCOOP の認知度を高めるために、これを投稿します。

SCOOP は、C++ プログラマーが OOP と GP の両方の世界を C++ でうまく連携させることにより、両方を最大限に活用できるようにするために考案されました。パフォーマンスが向上し、コードの表現力を高めるために使用できるため、主に科学的プログラミングを目的としています。

SCOOP により、C++ ジェネリック型は、従来のオブジェクト指向プログラミングの一見すべての側面を静的にエミュレートします。これは、テンプレート メソッドが機能するようになることを意味します。たとえば、適切にオーバーロードする機能や、(明らかに) カジュアルなテンプレート関数によって通常発生するエラー メッセージよりもはるかに適切なエラー メッセージを発行する機能などです。

また、条件付き継承などの面白いトリックを実行するためにも使用できます。

私が達成しようとしていたstatic_ptr<>のは、まさに一種の静的オブジェクト指向でした。SCOOP はそれをまったく新しいレベルに引き上げます。

興味のある方のために、私はこれについて話している 2 つの論文を見つけました:静的 C++ オブジェクト指向プログラミング (SCOOP) パラダイム 従来の OOP とジェネリック プログラミングセマンティクス駆動型ジェネリシティの混合利点: 静的 C++ オブジェクト指向プログラミング パラダイムの続編(SCOOP 2) .

ただし、このイディオム自体に欠点がないわけではありません。これは、あなたが何をしたかを人々が理解するのに苦労する可能性が最も高いため、最後の手段にするべき珍しいことの 1 つです。コードもより冗長になり、可能だと思っていたことができなくなる可能性があります。

ただし、本当の楽しみは言うまでもなく、状況によってはまだ役立つと思います。

幸せなテンプレート ハッキング。

于 2010-12-08T01:37:11.260 に答える