可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
In C++ is not possible to declare a static virtual function, neither cast a non-static function to a C style function pointer.
Now, I have a plain ol' C SDK that uses function pointers heavily.
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++.
Also this C SDKs function signature doesn't have a userData param.
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.
Edit
Answering to John Dibling, it would be great to be able to do this:
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
}
回答1:
You can make Base
be a class template that takes its function pointers from its template argument:
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 Base
using each derived class as the template argument:
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 Derived1
and Derived2
do 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:
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, if the 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."
回答2:
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.
回答3:
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();
回答4:
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.
回答5:
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
}
回答6:
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).
#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.
回答7:
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.
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:
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).
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++.
回答8:
Assuming that the C SDK allows you to pass it a void * to your data (and you should pass it your this pointer for the derived class:)
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.
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 this pointer to work with.
回答9:
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.
回答10:
The obvious way is like this, with FillPointers
implemented in each derived class.
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...
回答11:
If the C SDK wants you to perform operations without providing a userdata, then object-orientation is likely unnecessary and you should just write some functions. Else, time to find a new SDK.
回答12:
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
}
see also C++ static virtual members?