Alternatywa dla statycznych metod wirtualnych c++

W C++ nie Można zadeklarować statycznej funkcji wirtualnej, ani też nie można przypisać funkcji niestatycznej do wskaźnika funkcji w stylu C.

Teraz, mam zwykły ol ' C SDK, który używa wskaźników funkcji mocno.

Muszę wypełnić strukturę kilkoma wskaźnikami funkcji. Planowałem użyć klasy abstrakcyjnej z mnóstwem statycznych czystych metod wirtualnych i przedefiniować je w klasach pochodnych i wypełnić nimi strukturę. Dopiero wtedy zdałem sobie sprawę, że statyczne wirtualne nie są dozwolone w C++.

Również ta sygnatura funkcji C SDKs nie ma param userData.

Czy jest jakaś dobra alternatywa? Najlepsze, co przychodzi mi do głowy, to zdefiniowanie czystych metod wirtualnych GetFuncA (), GetFuncB (),... i niektóre statyczne elementy FuncA () / FuncB () w każdej klasie pochodnej, które byłyby zwracane przez Getfunx (). Następnie funkcja w klasie abstrakcyjnej wywołałaby te funkcje, aby uzyskać wskaźniki i wypełnić strukturę.

Edytuj Odpowiadasz użytkownikowi John Diabelskie, fajnie by było to zrobić:

class Base
{
    FillPointers() { myStruct.funA = myFunA; myStruct.funB = myFunB; ...}
private:
    CStruct myStruct;
    static virtual myFunA(...) = 0;
    static virtual myFunB(...) = 0;
};

class Derived1 : public Base
{
    Derived1() {  FillPointers();  }
    static virtual myFunA(...) {...};
    static virtual myFunB(...) {...};
};

class Derived2 : public Base
{
    Derived2() {  FillPointers();  }
    static virtual myFunA(...) {...};
    static virtual myFunB(...) {...};
};

int main()
{
    Derived1 d1;
    Derived2 d2;
    // Now I have two objects with different functionality
}
Author: Georg Fritzsche, 2010-04-27

12 answers

Możesz sprawić, że Base będzie szablonem klasy, który pobiera Wskaźniki funkcji z argumentu szablonu:

extern "C" {
struct CStruct
{
  void (*funA)(int, char const*);
  int (*funB)(void);
};
}

template <typename T>
class Base
{
public:
  CStruct myStruct;
  void FillPointers() {
    myStruct.funA = &T::myFunA;
    myStruct.funB = &T::myFunB;
  }
  Base() {
    FillPointers();
  }
};

Następnie zdefiniuj klasy pochodne, aby wynikały z instancji Base używając każdej klasy pochodnej jako argumentu szablonu:

class Derived1: public Base<Derived1>
{
public:
  static void myFunA(int, char const*) { }
  static int myFunB() { return 0; }
};

class Derived2: public Base<Derived2>
{
public:
  static void myFunA(int, char const*) { }
  static int myFunB() { return 1; }
};

int main() {
  Derived1 d1;
  d1.myStruct.funA(0, 0);
  d1.myStruct.funB();
  Derived2 d2;
  d2.myStruct.funA(0, 0);
  d2.myStruct.funB();
}

Ta technika jest znana jako ciekawie powtarzający się wzór szablonu. Jeśli zaniedbasz implementację jednej z funkcji w klasie pochodnej lub zmienisz podpis funkcji, pojawi się błąd kompilacji, który jest dokładnie tym, czego można się spodziewać, jeśli zaniedbałeś zaimplementować jedną z czystych funkcji wirtualnych z pierwotnego planu.

Konsekwencją tej techniki jest jednak to, że Derived1 i Derived2 nie mają wspólnej klasy bazowej. Dwie instancje Base<> nie są ze sobą w żaden sposób powiązane, jeśli chodzi o system typów. Jeśli chcesz, aby były powiązane, możesz wprowadzić inną klasę, która będzie służyć jako podstawa szablonu, a następnie umieścić wspólne rzeczy tam:

class RealBase
{
public:
  CStruct myStruct;
};

template <typename T>
class Base: public RealBase
{
  // ...
};

int main()
  RealBase* b;
  Derived1 d1;
  b = &d1;
  b->myStruct.funA(0, 0);
  b->myStruct.funB();
  Derived2 d2;
  b = &d2;
  b->myStruct.funA(0, 0);
  b->myStruct.funB();
}

Uwaga: statyczne funkcje Członkowskie niekoniecznie są kompatybilne ze zwykłymi wskaźnikami funkcji. Z mojego doświadczenia wynika, że jeśli kompilator akceptuje instrukcje assignment pokazane powyżej, to możesz mieć przynajmniej pewność, że są one zgodne dla tego kompilatora . Ten kod nie jest przenośny, ale jeśli działa na wszystkich platformach, które musisz obsługiwać, możesz uznać go za " wystarczająco przenośny."

 20
Author: Rob Kennedy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-28 18:16:14

Myślę, że wystarczy użyć zwykłej funkcji wirtualnej. Statyczna funkcja wirtualna nie ma sensu, ponieważ funkcja wirtualna jest rozwiązywana w czasie wykonywania. Co jest do rozwiązania, gdy kompilator wie dokładnie, czym jest funkcja statyczna?

W każdym razie sugerowałbym pozostawienie istniejącego rozwiązania wskaźnika funkcji na miejscu, jeśli to możliwe. Pomijając to, rozważ użycie normalnej funkcji wirtualnej.

 15
Author: Billy ONeal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-27 14:08:24

Nadal widzę zastosowanie statycznych metod wirtualnych, oto przykład:

class File
{
    static virtual std::string extension()  {return "";}
}

class ExecutableFile : public File
{
    // static because every executable has same extension
    static virtual std::string extension()  {return ".exe";}
}


std::string extension = "";

// needing static
extension = ExecutableFile::extension();

// not needing static nor virtual
ExecutableFile exeFile;
extension = exeFile.extension();

// needing virtual
File* pFile = &exeFile;
extension = pFile->extension();
 7
Author: Eno,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-05 16:40:24

Wspólny wzorzec podczas przekazywania wskaźnika funkcji (wywołania zwrotnego) do zestawu SDK C wykorzystuje fakt, że wiele takich funkcji pozwala na parametr void*, który jest "danymi użytkownika". Możesz zdefiniować swoje wywołania zwrotne jako proste funkcje globalne lub statyczne funkcje członka klasy. Następnie każde wywołanie zwrotne może rzucić parametr "dane użytkownika" do wskaźnika klasy bazowej, dzięki czemu można wywołać funkcję członka, która wykonuje pracę wywołania zwrotnego.

 5
Author: Permaquid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-27 14:22:05

Możesz po prostu przekazać funkcje bezpośrednio do konstruktora klasy bazowej:

class Base
{
    Base()(int (*myFunA)(...), int (*myFunB)(...)) 
    { myStruct.funA = funA; myStruct.funB = myFunB; ...}
private:
    CStruct myStruct;
};

class Derived1 : public Base
{
    Derived1() : Base (myFunA, myFunB) {}
    static myFunA(...) {...};
    static myFunB(...) {...};
};

class Derived2 : public Base
{
    Derived2() : Base (myFunA, myFunB) {}
    static myFunA(...) {...};
    static myFunB(...) {...};
};

int main()
{
    Derived1 d1;
    Derived2 d2;
    // Now I have two objects with different functionality
}
 4
Author: Eclipse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-27 14:59:49

Jeśli można określić Pochodny typ obiektu w czasie kompilacji, można użyć "Curiously Recurring Template Pattern" do osiągnięcia statycznego polimorfizmu. Dzięki temu podejściu nie ograniczasz się tylko do nadpisywania wirtualnych niestatycznych funkcji Członkowskich. Członkowie statyczni i niefunkcyjni są uczciwi. Można nawet nadpisać typy (ale podstawowy rozmiar obiektu nie może być funkcją tych typów).

#include <iostream>
#include <stdint.h>

struct VirtualBase {
    static const char* staticConst;
    static char* staticVar;
    static char* staticFun() { return "original static function"; }
    const char* objectConst;
    char* objectVar;
    virtual char* objectFun() { return "original object function"; }
    typedef int8_t Number;
    VirtualBase():
        objectConst("original object const"),
        objectVar("original object var")
    {}
    void virtual_dump(std::ostream& out=std::cout) {
        out << this->staticConst << std::endl;
        out << this->staticVar << std::endl;
        out << this->staticFun() << std::endl;
        out << this->objectConst << std::endl;
        out << this->objectVar << std::endl;
        out << this->objectFun() << std::endl;
        out << "sizeof(Number): " << sizeof(Number) << std::endl;
    }
};
const char* VirtualBase::staticConst = "original static const";
char* VirtualBase::staticVar = "original static var";

template <typename Derived>
struct RecurringBase: public VirtualBase {
    void recurring_dump(std::ostream& out=std::cout) {
        out << Derived::staticConst << std::endl;
        out << Derived::staticVar << std::endl;
        out << Derived::staticFun() << std::endl;
        out << static_cast<Derived*>(this)->staticConst << std::endl;
        out << static_cast<Derived*>(this)->staticVar << std::endl;
        out << static_cast<Derived*>(this)->staticFun() << std::endl;
        out << static_cast<Derived*>(this)->objectConst << std::endl;
        out << static_cast<Derived*>(this)->objectVar << std::endl;
        out << static_cast<Derived*>(this)->objectFun() << std::endl;
        out << "sizeof(Number): " << sizeof(typename Derived::Number) << std::endl;
    }
};

struct Defaults : public RecurringBase<Defaults> {
};

struct Overridden : public RecurringBase<Overridden> {
    static const char* staticConst;
    static char* staticVar;
    static char* staticFun() { return "overridden static function"; }
    const char* objectConst;
    char* objectVar;
    char* objectFun() { return "overridden object function"; }
    typedef int64_t Number;
    Overridden():
        objectConst("overridden object const"),
        objectVar("overridden object var")
    {}
};
const char* Overridden::staticConst = "overridden static const";
char* Overridden::staticVar = "overridden static var";

int main()
{
    Defaults defaults;
    Overridden overridden;
    defaults.virtual_dump(std::cout << "defaults.virtual_dump:\n");
    overridden.virtual_dump(std::cout << "overridden.virtual_dump:\n");
    defaults.recurring_dump(std::cout << "defaults.recurring_dump:\n");
    overridden.recurring_dump(std::cout << "overridden.recurring_dump:\n");
}

Oto Wyjście:

defaults.virtual_dump:
original static const
original static var
original static function
original object const
original object var
original object function
sizeof(Number): 1
overridden.virtual_dump:
original static const
original static var
original static function
original object const
original object var
overridden object function
sizeof(Number): 1
defaults.recurring_dump:
original static const
original static var
original static function
original static const
original static var
original static function
original object const
original object var
original object function
sizeof(Number): 1
overridden.recurring_dump:
overridden static const
overridden static var
overridden static function
overridden static const
overridden static var
overridden static function
overridden object const
overridden object var
overridden object function
sizeof(Number): 8

Jeśli nie można określić typu pochodnego do czasu uruchomienia, wystarczy użyć wirtualnej niestatycznej funkcji członka, aby zebrać statyczne lub niefunkcyjne informacje o klasie lub obiekcie.

 3
Author: Eric Mahurin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-31 22:11:40

Te rzeczy z pewnością by się przydały-mianowicie zmuszenie wszystkich obiektów w hierarchii klas do ujawnienia metody fabrycznej zamiast zwykłego konstruktora. Fabryki są bardzo przydatne, aby zapewnić, że nigdy nie budujesz nieprawidłowych obiektów, gwarancja projektu, której nie możesz wymusić prawie tak dobrze ze zwykłymi konstruktorami.

Aby zbudować "wirtualną statykę", należy ręcznie zbudować własny "statyczny stół v" we wszystkich obiektach, które tego potrzebują. Zwykłe funkcje Wirtualnego członka działają, ponieważ kompilator buduje tajną tabelę wskaźników funkcji zwanych VTABLE do wszystkich instancji klasy. Gdy budujesz obiekt "T", Wskaźniki funkcji w tej tabeli są przypisywane do adresów pierwszego przodka dostarczającego ten interfejs API. Nadpisanie funkcji staje się po prostu zastąpieniem oryginalnego wskaźnika w obiekcie otrzymanym z " new " nowym dostarczonym w klasie pochodnej. Oczywiście kompilator i runtime obsługują to wszystko za nas.

Ale w dawnych czasach przed nowoczesnym c++ (tak mi powiedziano), trzeba było samemu ustawić tę magię. I tak jest nadal w przypadku statyki wirtualnej. Dobra wiadomość jest taka - vtable, który budujesz ręcznie dla nich, jest w rzeczywistości prostszy niż "zwykły", jego wpisy nie są w żaden sposób droższe-w tym przestrzeń i wydajność - niż te dla funkcji Członkowskich. Po prostu zdefiniuj klasę bazową z jawnym zestawem wskaźników funkcji (statyczna tabela vtable) dla API, które chcesz obsługiwać:
template<typename T>
class VirtualStaticVtable {
private:
   typedef T (*StaticFactory)(KnownInputParameters params);

   StaticFactory factoryAPI;  // The 1 and only entry in my static v-table

protected:
   VirtualStaticVtable(StaticFactory factoryApi) : factoryAPI(factoryApi) {}
   virtual ~VirtualStaticVtable() {}
};

Teraz każdy obiekt, który powinna wspierać statyczne metody fabryczne mogą być wyprowadzone z tej klasy. Po cichu przechodzą we własnej fabryce do konstruktora, a to dodaje tylko 1 Wskaźnik do rozmiarów wynikowych obiektów (tak jak zwykły wpis VTable).

[[1]}Strousup and co. mogliby jeszcze dodać ten idiomatyczny wzorzec do języka podstawowego, gdyby chcieli. To nawet nie byłoby takie trudne. Każdy obiekt w takim "C+++" miałby po prostu 2 vtables zamiast 1 - 1 dla funkcji członowych przyjmujących " to " jako argument i 1 dla zwykłych wskaźników funkcji. Do tego dnia jednak utknęliśmy z ręcznymi vtables, tak jak byli to Starzy Programiści C w czasach przed c++.
 3
Author: Zack Yezek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-19 05:29:14

Zakładając, że C SDK pozwala Ci przekazać void * do Twoich danych (i powinieneś przekazać go to wskaźnik dla klasy pochodnej:)

class Base {

  public:

    void Initialize() { /* Pass /this/ and a pointer to myFuncAGate to your C SDK */ }

    virtual myFuncA()=0;

    // This is the method you pass to the C SDK:
    static myFuncAGate(void *user_data) {
        ((Base*)user_data)->myFuncA();
    }
};


class Derived1: public Base {
  public:
    virtual myFuncA() { ... } // This gets called by myFuncAGate()
};

Jeśli C SDK nie pozwala na przekazanie wskaźnika do danych, które są następnie przekazywane z powrotem do ciebie przez wywołania zwrotne, wtedy będziesz miał naprawdę trudny czas, aby to zrobić. Ponieważ w jednym ze swoich komentarzy wskazałeś, że tak rzeczywiście jest, masz raczej pecha. Sugerowałbym używanie prostych funkcji jako wywołań zwrotnych lub przeciążenie konstruktora i zdefiniowanie wielu metod statycznych. Nadal będziesz miał trudności z określeniem, jaki jest właściwy obiekt, z którym powinny pracować Twoje metody, gdy wywołania zwrotne są wywoływane przez kod C.

Jeśli opublikujesz więcej szczegółów na temat SDK, możesz dać ci bardziej odpowiednie sugestie, ale w ogólnym przypadku, nawet w przypadku metod statycznych, potrzebujesz jakiegoś sposobu uzyskania tego wskaźnika do pracy.

 2
Author: Ori Pessach,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-27 15:22:42

Funkcje wirtualne są zasadniczo wskaźnikami funkcji pod maską. Po prostu wskazują na różne funkcje dla różnych klas. Aby symulować zachowanie funkcji wirtualnej, należy mieć gdzieś przechowywany wskaźnik funkcji, a następnie "nadpisać" , po prostu przypisać go do innej funkcji.

Alternatywnie, możesz chcieć to przetestować, ale myślę, że interfejsy mają całkiem dobrą kompatybilność binarną. Może ujdzie ci to na sucho odsłaniając interfejs C++ składający się w całości z czystych funkcji wirtualnych, tak długo, jak wszystkie parametry i typy zwrotów mają spójny format binarny (np. Typu C). Nie jest standardem, ale może być wystarczająco przenośny.

 1
Author: AshleysBrain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-27 14:20:56

Oczywisty sposób jest taki, z FillPointers zaimplementowanym w każdej klasie pochodnej.

class Base
{
private:
    CStruct myStruct;
};

class Derived1 : public Base
{
 private:
    static FillPointers() { myStruct.funA = myFunA; myStruct.funB = myFunB; ...}
    Derived1() {  FillPointers();  }
    static myFunA(...) {...};
    static myFunB(...) {...};
};

Jednak prawdopodobnie można tego uniknąć używając magii szablonów...

 1
Author: Roddy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-27 15:05:53

Jeśli C SDK chce, abyś wykonywał operacje bez podawania danych użytkownika, orientacja obiektu jest prawdopodobnie niepotrzebna i powinieneś po prostu napisać kilka funkcji. W przeciwnym razie, czas znaleźć nowe SDK.

 1
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-27 16:28:37
class Base
{
    template<class T>
    FillPointers(T* dummy) { myStruct.funA = T::myFunA; myStruct.funB = T::myFunB; ...}
private:
    CStruct myStruct;
};

class Derived1 : public Base
{
    Derived1() {  FillPointers(this);  }
    static myFunA(...) {...};
    static myFunB(...) {...};
};

class Derived2 : public Base
{
    Derived2() {  FillPointers(this);  }
    static myFunA(...) {...};
    static myFunB(...) {...};
};

int main()
{
    Derived1 d1;
    Derived2 d2;
    // Now I have two objects with different functionality
}

Zobacz także C++ statyczne członkowie wirtualni?

 1
Author: Alsk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:31