S'il vous plaît considérer le code suivant:Interface référence à la mise en œuvre locale
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();
}
Sous GCC 4.4.1 (en utilisant -O2
), l'appel à B::go()
s'inline (aucune expédition virtuelle se produit.). Cela signifie que le compilateur reconnaît a_ref
indique en effet une variable de type B
. Une référence B
peut être utilisée pour pointer vers un C
, mais le compilateur est assez intelligent pour prévoir que ce n'est pas le cas, donc il optimise totalement l'appel de fonction, en définissant la fonction.
Parfait! C'est une optimisation incroyable.
Mais, alors, pourquoi GCC ne fait-il pas de même dans le cas suivant?
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
}
Des idées? Qu'en est-il des autres compilateurs? Ce type d'optimisation est-il courant? (Je suis très nouveau dans ce genre de vision du compilateur, donc je suis curieux)
Si le second a travaillé je pourrais créer des modèles vraiment super, comme ceux-ci:
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;
};
Ces modèles pourraient être utilisé pour éviter l'envoi virtuel dans de nombreux cas:
// 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;
}
Serait-il pratique d'implémenter cela? (Et ne vont pas à le dire est un micro-optimisation, car il pourrait bien être une énorme optimisation ..)
- modifier
Je viens de remarquer le problème avec static_ptr<>
n'a rien à voir avec le problème que je exposé. Le type de pointeur est conservé, mais il ne l'est toujours pas. Je suppose que GCC ne va pas aussi loin que nécessaire pour trouver static_ptr_container <> :: value n'est pas une référence ni un pointeur. Désolé pour ça. Mais la question reste toujours sans réponse.
- modifier
Je travaille sur une version de static_ptr<>
qui fonctionne réellement. J'ai changé le nom un peu, aussi:
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
}
La seule faiblesse est que l'utilisateur doit accéder ptr->value
pour obtenir l'objet réel. La surcharge operator ->()
ne fonctionne pas dans GCC. Toute méthode renvoyant une référence à l'objet réel, si elle est en ligne, casse l'optimisation. Quel dommage ..
Etes-vous inquiet du coût de l'envoi de la fonction virtuelle? Le coût n'est pratiquement pas mesurable. Dans la plupart des systèmes complexes, le coût de la recherche supplémentaire sera éclipsé par presque toutes les autres opérations qui bloquent le processeur (ce qui arrivera souvent). La clarté du code est beaucoup plus importante que la vitesse dans la majorité des cas (et le gain de vitesse supplémentaire ne vaudrait pas la complexité supplémentaire pour un humain de lire le code). –
J'ai beaucoup lu à ce sujet récemment et je pense que c'est un travail de compilateur ... Vous devriez lire à propos de "C++ Static Oriented-Object Programming". Ils utilisent beaucoup la métaprogrammation comme vous l'avez fait. –
Si vous regardez attentivement, vous verrez qu'il n'y a absolument aucune recherche supplémentaire en cours. Je suis allé dans cette ligne de raisonnement parce que je voulais écrire une interface commune à mettre en œuvre en utilisant trois bibliothèques de base différentes; puisque deux implémentations peuvent être instanciées, les utilisateurs pourraient mélanger les variables, en utilisant des instances de 'B' avec' A' et faire un désordre complet, par exemple. En outre, le système entier irait derrière une interface, encourageant la distribution de la fonction virtuelle pour chaque petit bit du système. Ce n'est pas ce que je veux; C'est une bibliothèque graphique 3D. –