4

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 ..

+0

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). –

+1

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. –

+0

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. –

Répondre

2

Ce n'est pas une réponse définitive, mais je pensais que je pourrais le poster de toute façon, car il pourrait être utile pour certaines personnes.

ommenter Julio Guerra a un idiome C de (qu'ils appellent un « paradigme » dans les journaux, mais je pense que c'est un peu trop) appelé statique C++ orientée objet Programmation (SCOOP). Je posterai ceci pour donner plus de visibilité à SCOOP.

SCOOP a été inventé pour permettre aux programmeurs C++ de tirer le meilleur parti des deux mondes OOP et GP en faisant en sorte que les deux jouent bien ensemble en C++.Il vise principalement la programmation scientifique en raison du gain de performance qu'il peut apporter et parce qu'il peut être utilisé pour augmenter l'expressivité du code. SCOOP rend les types génériques C++ imitant apparemment tous les aspects de la programmation orientée objet traditionnelle - statiquement. Cela signifie que les méthodes de template ont, par exemple, la possibilité d'être correctement surchargées et (apparemment) d'émettre des messages d'erreur beaucoup plus corrects que ceux qui sont habituellement causés par votre fonction de modèle occasionnel.

Il peut également être utilisé pour faire quelques astuces amusantes telles que l'héritage conditionnel. Ce que j'essayais d'accomplir avec static_ptr<> était précisément un type d'orientation d'objet statique. SCOOP déplace cela à un tout autre niveau.

Pour ceux qui sont intéressés, j'ai trouvé deux articles en parler: A Static C++ Object-Oriented Programming (SCOOP) Paradigm Mixing Benefits of Traditional OOP and Generic Programming et Semantics-Driven Genericity: A Sequel to the Static C++ Object-Oriented Programming Paradigm (SCOOP 2).

Ce langage n'est pas sans défauts, cependant: c'est une de ces choses inhabituelles qui devraient être votre dernier recours puisque les gens auront probablement du mal à comprendre ce que vous avez fait, etc. Votre code deviendra aussi plus verbeux et vous êtes susceptible de vous trouver incapable de faire des choses que vous pensiez être possible.

Je suis sûr que c'est encore utile dans certaines circonstances, cependant, pour ne pas mentionner vraiment amusant.

Joyeux modèle de piratage.

+0

+1: pour SCOOP et les liens – neuro

+0

J'ai eu une réponse, il a mis à jour les liens https://trac.lrde.org/olena/wiki/Static Amusez-vous :) –