4

Bitte beachten Sie den folgenden Code:Schnittstelle Bezug auf die lokale Umsetzung

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(); 
} 

Unter GCC 4.4.1 (mit -O2), wird der Anruf an B::go() inlined (dh keine virtuelle Versand geschieht.). Das bedeutet, dass der Compiler a_ref in der Tat deutet auf eine B Variable vom Typ erkennt. Eine B Referenz kann verwendet werden, um ein C-zu-Punkt, aber der Compiler ist intelligent genug, um vorauszusehen dies nicht der Fall ist, so ist es optimiert völlig den Funktionsaufruf entfernt, die Funktion inlining.

Great! Das ist eine unglaubliche Optimierung.

Aber dann, warum nicht das gleiche im folgenden Fall 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 
} 

Irgendwelche Ideen? Was ist mit anderen Compilern? Ist diese Art der Optimierung üblich? (Ich bin sehr neu in dieser Art von Compiler Einsicht, also bin ich neugierig)

Wenn der zweite Fall habe ich einige wirklich große Vorlagen erstellen können, wie diese:

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; 
}; 

Diese Vorlagen werden könnte verwendet, um virtuellen Versand in vielen Fällen zu vermeiden:

// 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; 
} 

Wäre es praktisch, das zu implementieren? (Und gehe nicht auf zu sagen, es ist eine Mikro-Optimierung, weil es gut eine riesige Optimierung sein kann ..)

- bearbeiten

Ich habe gerade bemerkt, das Problem mit static_ptr<> hat nichts mit dem Problem zu tun I ausgesetzt. Der Zeigertyp wird beibehalten, ist jedoch immer noch nicht inline. Ich denke, GCC nur nicht so tief geht, wie erforderlich, um herauszufinden, static_ptr_container <> :: Wert ist keine Referenz noch Zeiger. Das tut mir leid. Aber die Frage bleibt unbeantwortet.

- bearbeiten

Ich habe eine Version von static_ptr<> ausgearbeitet, die tatsächlich funktioniert. Ich habe den Namen ein wenig verändert, auch:

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 
} 

Die einzige Schwäche ist, dass der Benutzer ptr->value zuzugreifen das eigentliche Objekt zu erhalten. Überladen operator ->() funktioniert nicht in GCC. Jede Methode, die einen Verweis auf das tatsächliche Objekt zurückgibt, wenn es inline ist, unterbricht die Optimierung. Was für eine Schande.

+0

Sind Sie besorgt über die Kosten des virtuellen Funktionsversands? Die Kosten sind praktisch nicht messbar. In den meisten komplexen Systemen werden die Kosten der zusätzlichen Suche durch fast jede andere Operation in den Schatten gestellt, die den Prozessor blockiert (was viel passieren wird). Code-Klarheit ist in den meisten Fällen viel wichtiger als Geschwindigkeit (und der zusätzliche Geschwindigkeitsgewinn wäre die zusätzliche Komplexität für einen Menschen nicht wert, den Code zu lesen). –

+1

Ich lese kürzlich viel darüber und ich denke, es ist ein Compiler-Job ... Sie sollten über "C++ Static Oriented-Object Programming" lesen. Sie verwenden stark Metaprogrammierung wie Sie. –

+0

Wenn Sie genau hinsehen, werden Sie sehen, dass es keine zusätzliche Nachschau mehr gibt. Ich ging auf diese Argumentation ein, weil ich eine gemeinsame Schnittstelle schreiben wollte, die mit drei verschiedenen Basisbibliotheken implementiert werden sollte; Da zwei Implementierungen instanziiert werden können, könnten Benutzer die Variablen mischen, indem sie Instanzen von "B" mit "A" verwenden und zum Beispiel eine vollständige Unordnung machen. Zusätzlich würde das gesamte System hinter eine Schnittstelle treten, was zu einer virtuellen Funktionsverteilung für jedes kleine Teil des Systems führen würde. Das ist nicht was ich will; Es ist eine 3D-Grafikbibliothek. –

Antwort

2

Dies ist keine definitive Antwort, aber ich dachte, ich könnte es trotzdem posten, da es für einige Leute nützlich sein könnte.

Commenter Julio Guerra wies darauf hin, ein C++ Idiom (sie nennen es ein „Paradigma“ in den Zeitungen, aber ich denke, das ist ein bisschen zu viel) genannt Static C++-Objektorientierte Programmierung (SCOOP). Ich werde dies veröffentlichen, um SCOOP mehr Sichtbarkeit zu geben.

SCOOP wurde erfunden C++ Programmierer zu ermöglichen, sowohl das Beste aus der OOP zu bekommen und den GP Welten, indem sie sowohl in C++ gut miteinander spielen.Es zielt in erster Linie auf wissenschaftliche Programmierung ab, aufgrund des Leistungsgewinns, den es bringen kann, und weil es verwendet werden kann, um die Expressivität des Codes zu erhöhen.

SCOOP macht C++ generische Typen scheinbar alle Aspekte der traditionellen objektorientierten Programmierung - statisch. Dies bedeutet, dass Vorlagenmethoden eine Funktion erhalten, beispielsweise die Fähigkeit, ordnungsgemäß überladen zu werden, und (anscheinend) wesentlich genauere Fehlermeldungen ausgeben, als sie normalerweise von Ihrer Gelegenheitsvorlagenfunktion verursacht werden.

Es kann auch verwendet werden, um einige lustige Tricks wie bedingte Vererbung zu tun.

Was ich mit static_ptr<> erreichen wollte, war genau eine Art statischer Objektorientierung. SCOOP bewegt das auf ein ganz neues Level.

Für diejenigen, die interessiert sind, habe ich zwei Papiere gefunden, die darüber sprechen: A Static C++ Object-Oriented Programming (SCOOP) Paradigm Mixing Benefits of Traditional OOP and Generic Programming und Semantics-Driven Genericity: A Sequel to the Static C++ Object-Oriented Programming Paradigm (SCOOP 2).

Dieses Idiom ist jedoch nicht ohne seine eigenen Fehler: es ist eines dieser seltenen Dinge, die Ihr letzter Ausweg sein sollten, da die Leute wahrscheinlich eine schwierige Zeit haben werden, herauszufinden, was Sie getan haben usw. Ihr Code wird auch ausführlicher und Sie werden wahrscheinlich nicht in der Lage sein, Dinge zu tun, von denen Sie dachten, dass sie möglich wären.

Ich bin sicher, es ist immer noch nützlich, unter bestimmten Umständen, nicht zu erwähnen echte Spaß.

Happy Vorlage hacken.

+0

+1: für SCOOP und die Links – neuro

+0

Ich bekam eine Antwort, er aktualisierte die Links https://trac.lrde.org/olena/wiki/Static Viel Spaß :) –