次のコードを検討してください。
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
が、コンパイラはこれが当てはまらないことを予測できるほどスマートであるため、関数呼び出しを完全に最適化し、関数をインライン化します。B
C
すごい!それは信じられないほどの最適化です。
しかし、では、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 では機能しません。実際のオブジェクトへの参照を返すメソッドは、それがインラインの場合、最適化を中断します。お気の毒に..