替代c ++静态虚拟方法

时间:2010-04-27 14:05:30

标签: c++ static virtual function-pointers

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

现在,我有一个简单的'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
}

12 个答案:

答案 0 :(得分:22)

您可以使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();
}

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

然而,这种技术的结果是Derived1Derived2没有共同的基类。就类型系统而言,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();
}

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

答案 1 :(得分:16)

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

在任何情况下,如果可能的话,我建议将现有的函数指针解决方案留在原地。 Baring,考虑使用普通的虚函数。

答案 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();

答案 3 :(得分:6)

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

答案 4 :(得分: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
}

答案 5 :(得分: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");
}

这是输出:

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

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

答案 6 :(得分:4)

这些东西肯定是有用的 - 即强制类层次结构中的所有对象公开工厂方法而不是普通构造函数。工厂对于确保永远不会构建无效对象非常有用,这是一种设计保证,您无法对普通构造函数强制执行。

要构建“虚拟静态”,需要手动构建自己的“静态v表”到需要它的所有对象中。普通的虚拟成员函数可以正常工作,因为编译器会在类的所有实例中构建一个名为VTABLE的函数指针的秘密表。构建“T”对象时,此表中的函数指针将分配给提供该API的第一个祖先的地址。覆盖一个函数然后只需用派生类中提供的新对象替换你从'new'获得的对象中的原始指针。当然,编译器和运行时为我们处理这一切。

但是,回到现代c ++之前的旧时代(所以我被告知),你必须自己设定这个魔法。虚拟静力学的情况仍然存在。好消息是 - 你为他们手工制作的vtable实际上比'普通'更简单,它的条目在任何方面都不贵 - 包括空间和放大器。性能 - 比成员函数的性能。只需为要支持的API使用一组EXPLICIT函数指针(静态vtable)定义基类:

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条目一样)。

Strousup和co。如果他们愿意,仍然可以将这种惯用模式添加到核心语言中。它甚至不会那么难。对于成员函数,这样的“C +++”中的每个对象都只有2个vtable而不是1个,其中'this'作为参数,1表示普通函数指针。然而,直到那一天,我们仍然坚持使用手动vtable,就像旧的C程序员在c ++之前的日子一样。

答案 7 :(得分:3)

假设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的更多详细信息,可能会为您提供更多相关建议,但在一般情况下,即使使用静态方法,您也需要某种方法来获取指针与之合作。

答案 8 :(得分:2)

虚函数本质上是功能指针。他们只是指出不同类的不同功能。要模拟虚函数行为,请将函数指针存储在某处,然后“覆盖”它只是将其重新分配给某个不同的函数。

或者,你可能想测试一下,但我认为接口具有相当好的二进制兼容性。只要所有参数和返回类型都具有一致的二进制格式(例如C类型),您就可以完全暴露出完全由纯虚函数组成的C ++接口。它不是标准,但可能足够便携。

答案 9 :(得分:2)

显而易见的方法就是这样,每个派生类都实现了FillPointers

class Base
{
private:
    CStruct myStruct;
};

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

但是你可以使用一些模板魔法避免这种情况......

答案 10 :(得分:2)

如果C SDK希望您在不提供用户数据的情况下执行操作,则可能不需要面向对象,您应该只编写一些函数。此外,还有时间找到新的SDK。

答案 11 :(得分:2)

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++ static virtual members?