Schrompf hat geschrieben: ↑06.10.2020, 08:06
Was Du probieren könntest, ich aber nicht weiß, ob das vom Standard gedeckt ist: ein normaler Funktionszeiger mit explizitem
*this.
Code: Alles auswählen
using FuncPtr = void (*)(Base*);
FuncPtr func = &Base::Func;
Unerprobt. Vielleicht muss man auch hart reinterpreten oder cstylen, was dann aber ein Hinweis wäre, dass das so nicht vorgesehen ist.
Leider bin ich mir ziemlich sicher, dass es zumindest offiziell nicht erlaubt ist. GCC unterstützt das als
GNU-Extension, aber leider wird das nicht mal von Clang unterstützt.
DerAlbi hat geschrieben: ↑06.10.2020, 08:56
Schwierig. Das Problem am MFP ist, dass
sizeof(MFP) != sizeof(void*) ist. Soweit ich weiß, speichert dein MFP zusätzlich noch ein Offset zur vtable mit (auch bei nicht-virtuellen funktionen, da ist das Offset immer 0). Es gibt keine offizielle Methode einen MFP zu devirtualisieren - und selbst wenn man händisch devirtualisiert (dafür gibts Code für die spezifischen Compiler) muss man auch erst mal wissen welches Offset man wirklich in der vtable lesen will.
Mit virtuellen Funktionen hat die Verschiebung meines Wissens nichts zu tun.
Das Offset kommt ist für den Fall der Fälle, in dem Mehrfachvererbung verwendet wird, und ist bei Einfachvererbung immer 0. Es ist das Offset, um das der
this-Zeiger bei Klassen, die bei der Mehrfachvererbung nicht links stehen, angepasst werden muss. Hier ein Beispiel (
Godbolt-Link):
Code: Alles auswählen
struct A { int a; };
struct B { char b; void TestB(); };
struct C : A, B {};
using FPtr = void (C::*)();
FPtr Test() { return &B::TestB; }
Die Funktion
TestB, die im Kontext von Klasse
B als Funktionszeiger erstellt wird, weiß nichts davon, dass sie später im Kontext von Klasse
C verwendet wird. Das Speicherlayout ist ja wie folgt:
Code: Alles auswählen
struct C
{
// Hierhin soll bei einem Aufruf der "this"-Zeiger von "C"- und "A"-Member-Funktionen zeigen.
A a;
// Aber hierhin soll einem Aufruf der "this"-Zeiger von "B"-Member-Funktionen zeigen.
B b;
}
Der
this-Zeiger beim Aufruf
c.*(Test()) (also
&c, was natürlich zu einer
C-Instanz zeigt) muss also korrigiert werden, da die hiermit aufgerufene Funktion ja einen Zeiger zu einer
B-Instanz erwartet.
Bei Einfachvererbung sind Member-Funktionszeiger also ein klassischer Fall einer Abstraktion die nicht "Zero-Cost" ist.
Beim Microsoft-Compiler gibt es die von Krishty erwähnten Pragmas mit denen dieser Overhead theoretisch vermieden werden kann. Außerdem verwendet MSVC++ standardmäßig bei Einfachvererbung automatisch einfach gar keinen Offset! Ich vermute allerdings, dass das Standardverhalten eigentlich den C++-Standard verletzt. Man kann ja in obigen Beispiel den Funktionszeiger erst von einem
B::*-Zeiger zu einem
C::*-Zeiger und dann zu einem
A::*-Zeiger konvertieren (
Godbolt-Codebeispiel). Der MSVC-Compiler erzeugt dann netterweise nur die ominöse Warnung "cast between different pointer to member representations, compiler may generate incorrect code".