C++ 静态虚拟方法的替代方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2721846/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 00:29:16  来源:igfitidea点击:

Alternative to c++ static virtual methods

c++staticvirtualfunction-pointers

提问by raven

In C++ is not possible to declare a static virtual function, neither cast a non-static function to a C style function pointer.

在 C++ 中不能声明静态虚函数,也不能将非静态函数转换为 C 风格的函数指针。

Now, I have a plain ol' C SDK that uses function pointers heavily.

现在,我有一个大量使用函数指针的普通 C SDK。

I have to fill a structure with several function pointers. I was planning to use an abstract class with a bunch of static pure virtual methods, and redefine them in derived classes and fill the structure with them. It wasn't until then that I realized that static virtual are not allowed in C++.

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

Also this C SDKs function signature doesn't have a userData param.

此外,此 C SDK 函数签名没有 userData 参数。

Is there any good alternative? The best I can think of is defining some pure virtual methods GetFuncA(), GetFuncB(),... and some static members FuncA()/FuncB() in each derived class, which would be returned by the GetFuncX(). Then a function in the abstract class would call those functions to get the pointers and fill the structure.

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

EditAnswering to John Dibling, it would be great to be able to do this:

编辑对 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
}

采纳答案by Rob Kennedy

You can make Basebe a class template that takes its function pointers from its template argument:

您可以 makeBase是一个类模板,它从其模板参数中获取其函数指针:

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

Then, define your derived classes to descend from an instantiation of Baseusing each derived class as the template argument:

然后,定义您的派生类以从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();
}

That technique is known as the curiously recurring template pattern. If you neglect to implement one of the functions in a derived class, or if you change the function signature, you'll get a compilation error, which is exactly what you'd expect to get if you neglected to implement one of the pure virtual functions from your original plan.

这种技术被称为奇怪的重复模板模式。如果您忽略在派生类中实现其中一个函数,或者如果您更改函数签名,您将得到一个编译错误,这正是您在忽略实现一个纯虚函数时所期望得到的原计划中的功能。

The consequence of this technique, however, is that Derived1and Derived2do not have a common base class. The two instantiations of Base<>are not related in any way, as far as the type system is concerned. If you need them to be related, then you can introduce another class to serve as the base for the template, and then put the common things there:

这种技术的结果,但是,是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();
}

Beware:Static member functions are not necessarily compatible with ordinary function pointers. In my experience, ifthe compiler accepts the assignment statements shown above, then you can at least be confident that they're compatible for that compiler. This code isn't portable, but if it works on all the platforms you need to support, then you might consider it "portable enough."

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

回答by Billy ONeal

I think you just need to use a plain virtual function. A static virtual function does not make sense, because a virtual function is resolved at runtime. What's there to resolve when the compiler knows exactly what the static function is?

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

In any case, I would suggest leaving the existing function pointer solution in place if possible. Baring that, consider using a normal virtual function.

无论如何,如果可能,我建议保留现有的函数指针解决方案。除此之外,请考虑使用普通的虚函数。

回答by Eno

I still can see a use for static virtual methods, here an example:

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

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

回答by Permaquid

A common pattern when passing a function pointer (a callback) to a C SDK uses the fact that many such functions allow a void * parameter that is "user data". You can define your callbacks to be simple global functions, or static class member functions. Then each callback can cast the "user data" parameter to a base class pointer so you can call a member function that does the work of the callback.

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

回答by Eclipse

You could just pass the functions directly into the base class constructor:

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

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
}

回答by Eric Mahurin

If the derived type of an object can be determined at compile time, you can use the "Curiously Recurring Template Pattern" to achieve static polymorphism. With this approach you are not limited to just overriding virtual non-static member functions. Static and non-function members are fair game. You can even override types (but the base object size can't be a function of the those types).

如果可以在编译时确定对象的派生类型,则可以使用“Curious Recurring Template Pattern”来实现静态多态。使用这种方法,您不仅限于覆盖虚拟非静态成员函数。静态成员和非功能成员是公平的游戏。您甚至可以覆盖类型(但基础对象大小不能是这些类型的函数)。

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

Here is the output:

这是输出:

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

If the derived type cannot be determined until run-time, just use a virtual non-static member function to gather static or non-function info about the class or object.

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

回答by Zack Yezek

These things would certainly be useful- namely, to force all objects in a class hierarchy to expose a factory method instead of an ordinary constructor. Factories are very useful for ensuring you never build invalid objects, a design guarantee that you cannot enforce nearly as well with ordinary constructors.

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

To build 'virtual statics' requires building your own "static v-table" by hand into all the objects that need it. Ordinary virtual member functions work because the compiler builds a secret table of function pointers called the VTABLE into all instances of your class. When you build a "T" object, the function pointers in this table are assigned to the addresses of 1st ancestor providing that API. Overriding a function then simply becomes replacing the original pointer in the object you get from 'new' with the new one provided in the derived class. Of course, the compiler and runtime handle this all for us.

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

But, back in the really old days before modern c++ (so I'm told), you had to set this magic up yourself. And that's still the case for virtual statics. The good news is this- the vtable you build by hand for them is actually simpler than the 'ordinary' one, its entries are no more expensive in any way-including space & performance- than those for member functions. Just define the base class with an EXPLICIT set of function pointers (the static vtable) for the APIs you want supported:

但是,回到现代 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() {}
};

Now, every object that should support a static factory method can be derived from this class. They quietly pass in their own factory to their constructor, and it only adds 1 pointer to the resulting objects' sizes (just like an ordinary VTable entry).

现在,每个应该支持静态工厂方法的对象都可以从这个类派生出来。他们悄悄地将他们自己的工厂传递给他们的构造函数,并且它只添加 1 个指向结果对象大小的指针(就像一个普通的 VTable 条目)。

Strousup and co. could still add this idiomatic pattern to the core language if they wanted to. It wouldn't even be that hard. Every object in such a "C+++" would simply have 2 vtables instead of 1- 1 for member functions taking 'this' as an argument and 1 for ordinary function pointers. Until that day, however, we're stuck with manual vtables just like the old C-programmers were in the days before c++.

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

回答by Ori Pessach

Assuming that the C SDK allows you to pass it a void * to your data (and you should pass it your thispointer for the derived class:)

假设 C SDK 允许您将 void * 传递给您的数据(并且您应该将您的this指针传递给派生类:)

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

If the C SDK doesn't allow you to pass a pointer to your data which is then passed back to you through the callbacks, then you'll have a really hard time doing this. Since you indicated in one of your comments that this is indeed the case, you're pretty much out of luck. I would suggest using simple functions as callbacks, or overloading the constructor and defining multiple static methods. You'll still have a hard time determining what's the proper object your methods are supposed to work with when your callbacks are invoked by the C code.

如果 C SDK 不允许您传递指向数据的指针,然后通过回调将指针传回给您,那么您将很难做到这一点。由于您在其中一条评论中表示情况确实如此,因此您很不走运。我建议使用简单的函数作为回调,或者重载构造函数并定义多个静态方法。当您的回调被 C 代码调用时,您仍然很难确定您的方法应该使用的正确对象是什么。

If you post more details about the SDK it might be possible to give you more relevant suggestions, but in the general case, even with static methods, you need some way of obtaining a thispointer to work with.

如果您发布有关 SDK 的更多详细信息,则可能会为您提供更多相关建议,但在一般情况下,即使使用静态方法,您也需要某种方式来获取要使用的this指针。

回答by AshleysBrain

Virtual functions are essentially function pointers under-the-hood. They just point to different functions for different classes. To simulate virtual-function behavior, have a function pointer stored somewhere, then to 'override' it just reassign it to some different function.

虚函数本质上是引擎盖下的函数指针。它们只是为不同的类指向不同的函数。为了模拟虚函数行为,在某处存储一个函数指针,然后“覆盖”它只是将它重新分配给某个不同的函数。

Alternatively, you might want to test this, but I think interfaces have pretty good binary compatibility. You might get away with exposing a C++ interface composed entirely of pure virtual functions, so long as all the parameters and return types have a consistent binary format (eg. C types). It's not a standard, but it might be portable enough.

或者,您可能想对此进行测试,但我认为接口具有非常好的二进制兼容性。只要所有参数和返回类型具有一致的二进制格式(例如 C 类型),您就可以避免公开完全由纯虚函数组成的 C++ 接口。这不是标准,但它可能足够便携。

回答by Roddy

The obvious way is like this, with FillPointersimplemented in each derived class.

显而易见的方法是这样的,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(...) {...};
};

However you can probably avoid that using some template magic...

但是,您可以使用一些模板魔术来避免这种情况......