替代c ++静态虚拟方法

rav*_*ven 36 c++ virtual static function-pointers

在C++中不可能声明静态虚函数,也不能将非静态函数强制转换为C样式函数指针.

现在,我有一个简单的ol'C SDK,它大量使用函数指针.

我必须用几个函数指针填充一个结构.我计划使用一个带有一堆静态纯虚方法的抽象类,并在派生类中重新定义它们并用它们填充结构.直到那时我才意识到在C++中不允许使用静态虚拟.

此C SDKs函数签名也没有userData参数.

有什么好的选择吗?我能想到的最好的方法是在每个派生类中定义一些纯虚方法GetFuncA(),GetFuncB(),...和一些静态成员FuncA()/ FuncB(),它们将由GetFuncX()返回.然后抽象类中的函数将调用这些函数来获取指针并填充结构.

编辑 回答John Dibling,能够做到这一点真是太好了:

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
}
Run Code Online (Sandbox Code Playgroud)

Rob*_*edy 23

您可以创建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();
  }
};
Run Code Online (Sandbox Code Playgroud)

然后,定义派生类以从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();
}
Run Code Online (Sandbox Code Playgroud)

这种技术被称为奇怪的重复模板模式.如果忽略在派生类中实现其中一个函数,或者如果更改了函数签名,则会出现编译错误,如果您忽略实现其中一个纯虚拟,那么这就是您期望得到的错误原始计划中的功能.

然而,这种技术的结果是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();
}
Run Code Online (Sandbox Code Playgroud)

注意:静态成员函数不一定与普通函数指针兼容.根据我的经验,如果编译器接受上面显示的赋值语句,那么您至少可以确信它们与该编译器兼容.此代码不可移植,但如果它适用于您需要支持的所有平台,那么您可能会认为它"足够便携".


Bil*_*eal 16

我认为你只需要使用普通的虚函数.静态虚函数没有意义,因为虚拟函数在运行时被解析.当编译器确切地知道静态函数是什么时,有什么可以解决的?

无论如何,如果可能的话,我建议将现有的函数指针解决方案留在原地.在此,请考虑使用普通的虚拟功能.

  • **"[...]现在,我有一个简单的使用函数指针的C++ SDK.我必须填充一个带有几个函数指针的结构.我打算使用一个带有一堆静态纯虚拟的抽象类方法,并在派生类中重新定义它们并用它们填充结构.直到那时我才意识到静态虚拟在C++中是不允许的."**我会说它非常清楚. (2认同)

小智 9

我仍然可以看到静态虚拟方法的用法,这里有一个例子:

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();
Run Code Online (Sandbox Code Playgroud)


Per*_*uid 6

将函数指针(回调)传递给C SDK时的常见模式使用了许多此类函数允许void*参数为"用户数据"的事实.您可以将回调定义为简单的全局函数或静态类成员函数.然后,每个回调可以将"用户数据"参数强制转换为基类指针,以便您可以调用执行回调工作的成员函数.


Ecl*_*pse 6

您可以直接将函数传递给基类构造函数:

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
}
Run Code Online (Sandbox Code Playgroud)


Eri*_*rin 5

如果可以在编译时确定对象的派生类型,则可以使用"奇怪的重复模板模式"来实现静态多态.使用此方法,您不仅可以覆盖虚拟非静态成员函数.静态和非功能成员是公平的游戏.您甚至可以覆盖类型(但基础对象大小不能是这些类型的函数).

#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");
}
Run Code Online (Sandbox Code Playgroud)

这是输出:

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
Run Code Online (Sandbox Code Playgroud)

如果在运行时才能确定派生类型,只需使用虚拟非静态成员函数来收集有关类或对象的静态或非函数信息.