Member Function Pointer auf virtual Base::Function ist Derived::Function

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Member Function Pointer auf virtual Base::Function ist Derived::Function

Beitrag von DerAlbi »

Hallo Leute, obskures Problem:

ich habe folgende Struktur:

Code: Alles auswählen

struct Base
{
    virtual int Func()
    {
        return 1;
    }
};

struct Derived: Base
{
    Derived(): FuncBase_Ptr(&Base::Func) {} 

    int(Base::*FuncBase_Ptr)(); //Soll auf Base::Func zeigen!!
    int FuncBase()  //Leitet an Base::Func weiter
    {
        return Base::Func(); 
    }
    int Func() override //Böse Funktion, die sich dazwischen schiebt
    {
        return 2;
    }
};


int test()
{
    Derived d;
    Base* b = static_cast<Base*>(&d);
    //return d.FuncBase();  //1 (erwartet)
    //return b->Func();     //2 (erwartet)
    return (b->*(d.FuncBase_Ptr))();  //2 ???  WWWTTTFFF!?
}
oder auch hier: https://godbolt.org/z/G9c851

Schwer in Worten zu beschreiben:
Ich habe eine Basisklasse die eine virtuelle Funktion implementiert, die optional von einer abgeleiteten Klasse reimplementiert werden kann.
Ich benötigen in der abgeleiteten Klasse allerdings ein Member-Function-Pointer auf die originale Basisklassenimplementierung der Funktion.
Offensichtlich kann ich die Originalfunktion über Base::Func aufrufen, aber der Member-Function-Pointer auf &Base::Func zeigt auf Derived::Func, was so absolut nicht da steht. Wenn ich das wöllte, hätte ich Derived::Func geschrieben!

Hüüülfeeh
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Member Function Pointer auf virtual Base::Function ist Derived::Function

Beitrag von Spiele Programmierer »

Das geht in C++ leider nicht.

Ich fürchte, da hilft nur eine 2. (nicht virtuelle) Funktion in Base, die dann von Func aufgerufen wird und die du direkt für Funktionszeiger referenzieren kannst.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4852
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Member Function Pointer auf virtual Base::Function ist Derived::Function

Beitrag von Schrompf »

Da wird auch wirklich der Methoden-Ptr zu Base::Func() stehen, aber bei Member Function Pointern macht der Compiler sauberen Virtual Dispatch. Du kriegst also die ableitende Funktion, genauso wie wenn Du basePtr->Func() aufrufen würdest.

Wenn Du die Funktion der Basisklasse aufrufen willst, wäre das ohne FunctionPtr basePtr->Base::Func();. Vielleicht geht der Syntax auch mit der Member Function Pointer Invocation zu kombinieren.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: Member Function Pointer auf virtual Base::Function ist Derived::Function

Beitrag von DerAlbi »

man kann auch das virtual aus der Basisklasse streichen. Das geht bei mir, aber ist wohl nicht im Sinne des Erfinders. Die IDE beschwert sich auch drüber.
Spiele Programmierers Lösung habe ich zwischenzeitlich so gemacht - erzeugt auch nur overhead.
Den Syntaxvorschlag von Herrn Schrompfus habe ich im Compilerexploer nicht ans laufen bekommen.

Btw, selbst, wenn man in der Basisklasse (die nichts von Derived weiß) eine Funktion schreibt:

Code: Alles auswählen

static auto getFuncPtr() { return &Base::Func; }
ruft der so erzeugte MFP Derived::Func auf. Unglaublich.

"Leave no room for a lower level abstraction!!" Busted.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4852
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Member Function Pointer auf virtual Base::Function ist Derived::Function

Beitrag von Schrompf »

Es ist ein virtueller Funktionsaufruf. Der muss genau so funktionieren, wie er bei Dir funktioniert, sonst wäre das ein Compilerbug. Wenn Du nicht willst, dass das Ding virtuell dispatched, dann mach die Funktion nicht virtuell.

Die klassische Lösung hierfür wäre, die Arbeit in eine andere Funktion auszulagern, und stattdessen die zu verpointern.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: Member Function Pointer auf virtual Base::Function ist Derived::Function

Beitrag von DerAlbi »

Ich stimme dir da voll zu. Allerdings sollte es mMn eine Möglichkeit geben an die verdeckten Funktionen ran zu kommen.
..kommt man ja auch, per Base::Func()! Es ist doch im Prinzip ein fehlendes Feature diesen Aufruf nicht per Funktionspointer machen zu können.
Das virtual weg zu lassen ist in meinem Fall möglich, aber nicht prinzipiell korrekt, bzw ändert es die Bedeutung des Programms - das könnte genauso gut Konsequenzen haben. Und der Umweg über einen Wrapper verstößt, wie gesagt, gegen den zitterten C++-Grundsatz.
Benutzeravatar
xq
Establishment
Beiträge: 1581
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: Member Function Pointer auf virtual Base::Function ist Derived::Function

Beitrag von xq »

Code: Alles auswählen

return d.Base::Func();
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: Member Function Pointer auf virtual Base::Function ist Derived::Function

Beitrag von DerAlbi »

Ja, das geht, kein Ding.
Nun gib mir einen Member-Function-Pointer, der den Aufruf genauso macht wie die Zeile, wenn man den MFP aufruft.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4852
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Member Function Pointer auf virtual Base::Function ist Derived::Function

Beitrag von Schrompf »

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.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: Member Function Pointer auf virtual Base::Function ist Derived::Function

Beitrag von DerAlbi »

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.
Benutzeravatar
Krishty
Establishment
Beiträge: 8237
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Member Function Pointer auf virtual Base::Function ist Derived::Function

Beitrag von Krishty »

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.
Weitgehend korrekt. Unter Visual Studio ist’s sogar noch schlimmer (siehe vtordisp-Option /vmb, /vmg (Representation Method)-Option und pointers-to-members-Pragma). Ich würde nur raten: Don’t. Just don’t.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Member Function Pointer auf virtual Base::Function ist Derived::Function

Beitrag von Spiele Programmierer »

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