我有这个课程:
class Foo
{
...
};
class Foo1 : public Foo
{
...
};
...
class FooN : public Foo
{
...
};
是否可以拥有具有这些签名的函数数组:
void f1(Foo1*){}
...
void fN(FooN*){}
如果这些函数是非静态成员函数而不是常规函数,是否有任何变化?我不认为这会改变什么。
谢谢!
我有这个课程:
class Foo
{
...
};
class Foo1 : public Foo
{
...
};
...
class FooN : public Foo
{
...
};
是否可以拥有具有这些签名的函数数组:
void f1(Foo1*){}
...
void fN(FooN*){}
如果这些函数是非静态成员函数而不是常规函数,是否有任何变化?我不认为这会改变什么。
谢谢!
在此处编辑基于非虚拟功能的替代解决方案。
该类型void(*)(Foo*)
不能转换为该类型void(*)(Bar*)
,这是有充分理由的。
你应该让你的所有函数都接受一个Interface*
参数,并且所有的FooN
都应该来自Interface
struct Interface {
virtual ~ Interface () {}
// ...
};
struct Foo1 : public Interface {
// ...
};
struct Foo2 : public Interface {
// ...
};
void f1 (Interface *);
void f2 (Interface *);
void (*functions)(Interface*) [] = {f1, f2};
functions[0] (new Foo1 ());
functions[0] (new Foo2 ());
functions[1] (new Foo1 ());
functions[1] (new Foo2 ());
的实现可以在运行f1
时通过使用和检查来检查f2
它们的参数是否是特定的实现。在编译时检查的唯一方法是制作和采用特定类型,而不是将它们放在匿名数组中,而是显式调用它们。dynamic_cast
nullptr
f1
f2
要回答您问题的第二部分-是的,它们是否是非静态成员函数很重要,因为指针的大小不是恒定的
您可以使用函数对象。请参阅下面的示例,了解如何自己做。如果你喜欢这个想法,你应该看看 boost.signal/boost.bind 和 c++ 0x 对应物。
class Foo1 {};
class Foo2 {};
class Foo3 {};
void func1(Foo1*) {}
void func2(Foo2*) {}
void func3(Foo3*) {}
class FuncObjBase {
public:
virtual void operator()() = 0;
};
template <class T>
class FuncObj : public FuncObjBase {
public:
typedef void (*Funcptr)(T*);
FuncObj(T* instance, Funcptr funcptr) : m_Instance(instance), m_Func(funcptr) {}
virtual void operator()() { m_Func(m_Instance); }
private:
T* m_Instance;
Funcptr m_Func;
};
int main(int argc, char *argv[])
{
Foo1 foo1;
Foo2 foo2;
Foo3 foo3;
FuncObjBase* functions[3];
functions[0] = new FuncObj<Foo1>(&foo1, func1);
functions[1] = new FuncObj<Foo2>(&foo2, func2);
functions[2] = new FuncObj<Foo3>(&foo3, func3);
for(unsigned int i = 0; i < 3; i++) {
(*functions[i])();
}
return 0;
}
C++ 是一种静态类型语言,它包括函数的类型。在每一行代码中,C++ 编译器必须能够确定函数签名是否有效以及调用哪个函数(或指针)。
为了做你所说的,你需要能够在运行时恢复指针的类型,基于在运行时放入数组的值。多态性是您在运行时可以获得的唯一与类型相关的东西。甚至那只处理类的类型。确切地调用哪个函数是没有争议的。
你能做的最好的事情就是使用类似boost::variant
. 您可以将一组特定的函数原型存储在变体中,可能使用boost::function
. 但是,它只是一个有界集合,而不是任何任意函数类型。并且调用它们会相当困难,因为您首先必须验证变体确实是预期的函数类型,然后调用它。
另一种选择是使用boost::any
. 除了这里,类型可以是任何函数类型。同样,调用它需要将其转换为预期的函数类型之一。问题很复杂,因为函数类型实际上可以是任何东西。因此,如果它不是预期的函数类型之一,则必须提供后备。
如果函数列表很小并且编译时确定,则可以使用 aboost::tuple
作为临时“数组”。但是,您必须使用模板元编程来迭代它们。当然,如果是这种情况,您可以只使用包含适当类型的函数指针的结构。
您可以使用可变参数模板在 C++11 中执行此操作。检查我的答案,这与您想要的类似,但地图位于:https ://stackoverflow.com/a/33837343/1496826
您可以使用函数对象。
例如 Boost.Signal 或来自 C++0x / TR1 的那些
您可以f1
通过fN
其特定参数类的成员创建函数,将它们命名为相同并使用虚拟调度来调用正确的函数。然后,您只需将指向成员函数的指针填充到数组中。
你想要的是协变参数类型。这在 C++ 中不受支持,因为它破坏了类型安全。为了更好地理解这一点,让我们举一个简单的例子:
struct Vehicle {};
struct Tricycle : Vehicle {};
struct Tank : Vehicle {};
void drive(Vehicle const & b) { ... }
void giveToChild(Tricycle const & b) { ... }
这里我们有一个简单的类型层次结构,以及两个分别引用基类和派生类之一的函数。现在,如果您的要求被允许,我们可以执行以下操作:
typedef void (*funcPtr)(Vehicle const &);
funcPtr = &giveToChild; // this is not allowed
funcPtr(Tank()); // oops, I just gave a tank to my child!
该语言可以实现某种运行时类型验证,但这不是 C++ 的工作方式。
但是,可以允许反向转换(逆变)而没有任何问题(实际上,C# 委托允许它),但由于某些我不知道的原因在 C++ 中是不可能的。这是它允许的示例:
typedef void (*funcPtr)(Tricycle const &);
funcPtr = &drive; // this could be allowed, but is not (in C++)
funcPtr(Tricycle()); // I can only drive a tricycle, but that's ok since it's a
// vehicle and I know how to drive all vehicles
所以基本上,如果不求助于在调用原始函数之前检查参数类型的转发函数,你想要实现的目标是不可能的:
void forwardFN(Foo * f)
{
FooN * instance = dynamic_cast<FooN *>(f);
if (instance) fN(instance);
else throw type_exception();
}
我为这个问题找到了这个解决方法:
#include <iostream>
#include <vector>
class Foo
{
};
class Foo1 : public Foo
{
};
class Foo2 : public Foo
{
};
class Foo3 : public Foo
{
};
void f1(Foo1*)
{
std::cout<<"f1\n";
}
void f2(Foo2*)
{
std::cout<<"f2\n";
}
void f3(Foo3*)
{
std::cout<<"f3\n";
}
template<typename T>
void AddPointer(std::vector<typename void (*)(Foo*)>& fPointers, T function)
{
fPointers.push_back(reinterpret_cast<void (*)(Foo*)>(function));
}
void main()
{
std::vector<typename void (*)(Foo*)> fPointers;
AddPointer(fPointers, f1);
AddPointer(fPointers, f2);
AddPointer(fPointers, f3);
Foo1 foo1;
Foo2 foo2;
Foo3 foo3;
fPointers[0](&foo1);
fPointers[1](&foo2);
fPointers[2](&foo3);
}
我建议使用 astd::tuple
而不是 astd::array
或 C 数组。使用 astd::tuple
您可以存储不同类型的元素。
这是一种类型安全且强制客户端代码正确的通用方法。
class Manager {
public:
typedef int /* or whatever */ ID;
template <typename Function>
static void save (Function * f, ID id) {
functions <Function> () .add (id, f);
}
template <typename Function>
static Function * get (ID id) {
return functions <Function> () .get (id);
}
private:
template <typename Function>
class FunctionStore {
public:
void add (Function *, ID);
Function * get (ID);
private:
// or vector, if you know ID is int.
std :: map <ID, Function *> m_functions;
};
// type_index is C++11 but you can implement it in C++03.
// void* here is unpleasant but you can improve it, RAII it.
typedef std :: map <std :: type_index, void *> Store;
static Store m_store;
template <typename Function>
FunctionStore <Function> & functions () {
FunctionStore <Function> * fs;
Store :: iterator i = m_store .find (typeid Function);
if (m_store .end () == i) {
fs = new FunctionStore <Function> ();
m_store [typeid Function] = fs;
}
else {
// This void* cast is OK because it's internally controlled
// and provably correct.
// We don't have to trust the library to not abuse it.
fs = static_cast <FunctionStore<Function>*> (i -> second);
}
return *fs;
}
};
// In the library
void foo1 (Foo *);
void bar1 (Bar *);
void foo2 (Foo *);
void bar2 (Bar *);
void init () {
Manager :: save (foo1, 1);
Manager :: save (foo2, 2);
Manager :: save (bar1, 1);
Manager :: save (bar2, 2);
Manager :: get <void(Foo*)> (1) (new Foo ()); // OK, calls foo1
Manager :: get <void(Foo*)> (1) (new Bar ()); // Will not compile
Manager :: get <void(Bar*)> (2) (new Bar ()); // OK, calls bar2
}
如果您不想要 in 查找的开销m_store
(和/或想要避免void
in Manager::Store
),您可以将Manager
自己设为模板类,缺点是您现在必须注意静态m_store
定义。如果您知道客户端将仅使用给定的一组Function
签名,则可以。
void init () {
Manager <void(Foo*)> :: save (foo1, 1);
Manager <void(Foo*)> :: save (foo2, 2);
Manager <void(Foo*)> :: save (bar1, 1); // Won't compile
Manager <void(Bar*)> :: save (bar1, 1);
Manager <void(Bar*)> :: save (bar2, 2);
Manager <void(Foo*)> :: get (1) (new Foo ()); // OK, calls foo1
Manager <void(Foo*)> :: get (1) (new Bar ()); // Will not compile
Manager <void(Bar*)> :: get (2) (new Bar ()); // OK, calls bar2
}
该init
函数说明了我在其他帖子的评论中提出的关键点:如果您知道要使用哪些类型调用函数,那么您就知道要从哪个函数集合中获取。没有必要尝试将它们全部塞在一起,这样做只会损害您的类型安全。