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