Альтернатива статическим виртуальным методам c ++ - PullRequest
36 голосов
/ 27 апреля 2010

В C ++ невозможно объявить статическую виртуальную функцию, также не приведите нестатическую функцию к указателю функции в стиле C.

Теперь у меня есть простой старый SDK C, который интенсивно использует указатели на функции,

Я должен заполнить структуру несколькими указателями на функции.Я планировал использовать абстрактный класс с кучей статических чисто виртуальных методов, переопределить их в производных классах и заполнить ими структуру.Только тогда я понял, что статические виртуальные недопустимы в C ++.

Также эта сигнатура функции C SDK не имеет параметра userData.

Есть ли хорошая альтернатива?Лучшее, что я могу придумать, - это определить некоторые чисто виртуальные методы GetFuncA (), GetFuncB (), ... и некоторые статические члены FuncA () / FuncB () в каждом производном классе, которые будут возвращены GetFuncX ().Тогда функция в абстрактном классе будет вызывать эти функции, чтобы получить указатели и заполнить структуру.

Редактировать Отвечая Джону Диблингу, было бы здорово сделать это:

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
}

Ответы [ 12 ]

23 голосов
/ 27 апреля 2010

Вы можете сделать Base шаблоном класса, который берет указатели на функции из аргумента шаблона:

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

Затем определите ваши производные классы для перехода от экземпляра Base, используя каждый производный класс в качестве аргумента шаблона:

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

Эта техника известна как любопытно повторяющийся шаблон . Если вы пренебрегаете реализацией одной из функций в производном классе или изменяете сигнатуру функции, вы получите ошибку компиляции, которая является именно тем, что вы ожидаете получить, если пренебрегли реализацией одной из чисто виртуальных функций. функции из вашего первоначального плана.

Следствием этой техники, однако, является то, что Derived1 и Derived2 не имеют общего базового класса. Два экземпляра Base<> не имеют никакого отношения к системе типов. Если вам нужно, чтобы они были связаны, то вы можете ввести другой класс, который будет служить основой для шаблона, а затем поместить туда общие элементы:

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

Осторожно: Статические функции-члены не обязательно совместимы с обычными указателями на функции. По моему опыту, , если компилятор принимает операторы присваивания, показанные выше, то вы, по крайней мере, можете быть уверены, что они совместимы для этого компилятора . Этот код не переносим, ​​но если он работает на всех платформах, которые вам нужно поддерживать, вы можете считать его «достаточно переносимым».

16 голосов
/ 27 апреля 2010

Я думаю, вам просто нужно использовать простую виртуальную функцию. Статическая виртуальная функция не имеет смысла, потому что виртуальная функция разрешается во время выполнения. Что нужно решить, когда компилятор точно знает, что такое статическая функция?

В любом случае, я бы предложил оставить существующее решение с указателем функции на месте, если это возможно. Обнажая это, рассмотрите возможность использования обычной виртуальной функции.

9 голосов
/ 05 сентября 2014

Я все еще вижу использование статических виртуальных методов, вот пример:

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();
6 голосов
/ 27 апреля 2010

Вы можете просто передать функции непосредственно в конструктор базового класса:

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
}
6 голосов
/ 27 апреля 2010

Обычный шаблон при передаче указателя функции (обратного вызова) в SDK C использует тот факт, что многие такие функции допускают параметр void *, который является «пользовательскими данными». Вы можете определить ваши обратные вызовы как простые глобальные функции или статические функции-члены класса. Затем каждый обратный вызов может привести параметр «пользовательские данные» к указателю базового класса, чтобы вы могли вызвать функцию-член, которая выполняет функцию обратного вызова.

5 голосов
/ 31 января 2012

Если производный тип объекта может быть определен во время компиляции, вы можете использовать «Любопытно повторяющийся шаблон» для достижения статического полиморфизма. При таком подходе вы не ограничиваетесь просто переопределением виртуальных нестатических функций-членов. Статические и нефункциональные члены являются честной игрой. Вы даже можете переопределять типы (но размер базового объекта не может быть функцией этих типов).

#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");
}

Вот вывод:

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

Если производный тип не может быть определен до времени выполнения, просто используйте виртуальную нестатическую функцию-член для сбора статической или нефункциональной информации о классе или объекте.

4 голосов
/ 19 февраля 2014

Эти вещи, безусловно, были бы полезны, а именно - заставить все объекты в иерархии классов предоставлять фабричный метод вместо обычного конструктора. Фабрики очень полезны для того, чтобы гарантировать, что вы никогда не создадите недопустимые объекты, что является гарантией того, что вы не сможете применить их так же хорошо, как обычные конструкторы.

Чтобы построить «виртуальную статику», нужно вручную собрать собственную «статическую виртуальную таблицу» во все объекты, которые в ней нуждаются. Обычные виртуальные функции-члены работают, потому что компилятор создает секретную таблицу указателей функций, называемую VTABLE, во все экземпляры вашего класса. При создании объекта «T» указатели на функции в этой таблице назначаются адресам первого предка, предоставляющего этот API. Затем переопределение функции просто заменяет исходный указатель в объекте, который вы получаете из «нового», на новый, предоставленный в производном классе. Конечно, компилятор и среда выполнения обрабатывают все это для нас.

Но, в очень старые времена, до современного c ++ (так мне сказали), вам пришлось настроить это волшебство самостоятельно. И это все еще касается виртуальной статики. Хорошая новость заключается в том, что таблица, которую вы создаете для них вручную, на самом деле проще, чем «обычная», ее записи ни в коем случае не дороже, включая пространство и производительность, чем записи для функций-членов. Просто определите базовый класс с набором функциональных указателей EXPLICIT (статическая таблица) для API, которые вы хотите поддерживать:

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

Теперь каждый объект, который должен поддерживать статический метод фабрики, может быть получен из этого класса. Они спокойно передают свои собственные фабрики своему конструктору, и он только добавляет 1 указатель к размерам результирующих объектов (как обычная запись VTable).

Струсуп и Ко мог бы добавить этот идиоматический паттерн к основному языку, если бы захотел. Это даже не будет так сложно. Каждый объект в таком «C +++» просто имел бы 2 vtables вместо 1-1 для функций-членов, принимающих this в качестве аргумента, и 1 для обычных указателей на функции. Однако до того дня мы застряли с ручными таблицами vtable, как это было со старыми программистами на C до появления c ++.

3 голосов
/ 27 апреля 2010

Предполагая, что C SDK позволяет вам передать это void * в ваши данные (и вы должны передать ему ваш этот указатель для производного класса:)

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

Если C SDK не позволяет вам передавать указатель на ваши данные, которые затем передаются обратно через обратные вызовы, тогда вам будет очень трудно это сделать. Поскольку вы указали в одном из ваших комментариев, что это действительно так, вам в значительной степени не повезло. Я бы предложил использовать простые функции в качестве обратных вызовов или перегрузить конструктор и определить несколько статических методов. Вам все еще будет трудно определить, с каким именно объектом должны работать ваши методы, когда ваши обратные вызовы вызываются кодом C.

Если вы опубликуете более подробную информацию о SDK, возможно, будет возможно дать вам более подходящие предложения, но в общем случае, даже при использовании статических методов, вам потребуется какой-то способ получения указателя this для работы с.

2 голосов
/ 11 мая 2010
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
}

см. Также C ++ статические виртуальные члены?

2 голосов
/ 27 апреля 2010

Если C SDK хочет, чтобы вы выполняли операции без предоставления пользовательских данных, то объектная ориентация, скорее всего, не нужна, и вам просто нужно написать некоторые функции. Иначе время найти новый SDK.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...