I have the code as below. I have a abstract template class Foo and two subclasses (Foo1 and Foo2) which derive from instantiations of the template. I wish to use pointers in my program that can point to either objects of type Foo1 or Foo2, hence I created an interface IFoo.
我有如下代码。我有一个抽象模板类Foo和两个子类(Foo1和Foo2),它们派生自模板的实例化。我希望在我的程序中使用指向Foo1或Foo2类型的对象的指针,因此我创建了一个接口IFoo。
My problem is I'm not sure how to include functionB in the interface, since it is dependant on the template instantiation. Is it even possible to make functionB accessible via the interface, or am I attempting the impossible?
我的问题是我不确定如何在界面中包含functionB,因为它依赖于模板实例化。甚至可以通过界面使functionB可访问,还是我尝试不可能?
Thank you very much for your help.
非常感谢您的帮助。
class IFoo {
public:
virtual functionA()=0;
};
template<class T>
class Foo : public IFoo{
public:
functionA(){ do something; };
functionB(T arg){ do something; };
};
class Foo1 : public Foo<int>{
...
};
class Foo2 : public Foo<double>{
...
};
5 个解决方案
#1
10
You are actually attempting the impossible.
你实际上是在尝试不可能的事。
The very heart of the matter is simple: virtual
and template
do not mix well.
事情的核心很简单:虚拟和模板混合不好。
-
template
is about compile-time code generation. You can think of it as some kind of type-aware macros + a few sprinkled tricks for meta programming. -
virtual
is about runtime decision, and this require some work.
模板是关于编译时代码生成的。您可以将其视为某种类型感知宏+一些用于元编程的技巧。
虚拟是关于运行时决策,这需要一些工作。
virtual
is usually implemented using a virtual tables (think of a table which lists the methods). The number of methods need be known at compile time and is defined in the base class.
virtual通常使用虚拟表实现(想象一下列出方法的表)。需要在编译时知道方法的数量,并在基类中定义。
However, with your requirement, we would need a virtual table of infinite size, containing methods for types we haven't seen yet and that will only be defined in the years to come... it's unfortunately impossible.
但是,根据您的要求,我们需要一个无限大小的虚拟表,其中包含我们尚未看到的类型的方法,并且只会在未来几年内定义......遗憾的是不可能。
And if it were possible ?
如果可能的话?
Well, it just would not make sense. What happens when I call Foo2
with an int
? It's not meant for it! Therefore it breaks the principle that Foo2
implements all the methods from IFoo
.
嗯,这没有意义。当我用int调用Foo2时会发生什么?它并不意味着它!因此,它打破了Foo2实现IFoo所有方法的原则。
So, it would be better if you stated the real problem, this way we could help you at a design level rather than at a technical level :)
所以,如果你说出真正的问题会更好,这样我们可以在设计层面而不是技术层面帮助你:)
#2
5
Easiest way is to make your interface templated.
最简单的方法是使您的界面模板化。
template <class T>
class IFoo {
public:
virtual void functionA()=0;
virtual void functionB(T arg){ do something; };
};
template<class T>
class Foo : public IFoo<T>{
public:
void functionA(){ do something; };
void functionB(T arg){ do something; };
};
#3
4
Since functionB's argument type must be known in advance, you have only one choice: Make it a type which can hold every possible argument. This is sometimes called a "top type" and the boost libraries have the any
type which gets quite close to what a top type would do. Here is what could work:
由于必须事先知道functionB的参数类型,因此您只有一个选择:使其成为可以包含所有可能参数的类型。这有时被称为“*类型”,并且boost库具有与*类型相近的任何类型。这是可行的:
#include <boost/any.hpp>
#include <iostream>
using namespace boost;
class IFoo {
public:
virtual void functionA()=0;
virtual void functionB(any arg)=0; //<-can hold almost everything
};
template<class T>
class Foo : public IFoo{
public:
void functionA(){ };
void real_functionB(T arg)
{
std::cout << arg << std::endl;
};
// call the real functionB with the actual value in arg
// if there is no T in arg, an exception is thrown!
virtual void functionB(any arg)
{
real_functionB(any_cast<T>(arg));
}
};
int main()
{
Foo<int> f_int;
IFoo &if_int=f_int;
if_int.functionB(10);
Foo<double> f_double;
IFoo &if_double=f_double;
if_int.functionB(10.0);
}
Unfortunately, any_cast
does not know about the usual conversions. For example any_cast<double>(any(123))
throws an exception, because it does not even try to convert the integer 123 to a double. If does not care about conversions, because it is impossible to replicate all of them anyway. So there are a couple of limitations, but it is possible to find workarounds if necessary.
不幸的是,any_cast不知道通常的转换。例如,any_cast
#4
0
I don't think you can get what you want. Think of this if you were to implement your suggestion: if you have a pointer to an IFoo
instance and you call functionB()
, what type parameter should you give it? The underlying problem is that Foo1::functionB
and Foo2::functionB
have different signatures and do different things.
我认为你不能得到你想要的东西。如果你要实现你的建议,可以考虑一下:如果你有一个指向IFoo实例的指针而你调用了functionB(),你应该给它什么类型的参数?根本问题是Foo1 :: functionB和Foo2 :: functionB有不同的签名并做不同的事情。
#5
0
You can achieve something comparable by wrapping the IFoo* pointer in a class and exposing the functionality via generic template functions of the non-templated wrapper class:
通过将IFoo *指针包装在类中并通过非模板化包装类的通用模板函数公开功能,可以实现类似的功能:
#include <assert.h>
// interface class
class IFoo {
public:
virtual int type() const = 0; // return an identifier for the template parameter
virtual bool functionA() = 0;
};
// This function returns a unique identifier for each supported T
template <typename T> static int TypeT() { static_assert("not specialized yet"); }
template <> static int TypeT<bool>() { return 0; }
template <> static int TypeT<double>() { return 1; }
//template <> static int TypeT<...>() { ... }
// templated class
template <typename T> class FooT : public IFoo {
public:
int type() const override { return TypeT<T>(); }
bool functionA() override { return true; }
// not in interface
bool functionB(T arg) { return arg == T(); }
};
// function to create an instance of FooT (could also be static function in FooT)
static IFoo* CreateFooT(int type)
{
switch (type)
{
case 0: return new FooT<bool>();
case 1: return new FooT<double>();
//case ...: return new FooT<...>();
default: return nullptr;
}
}
// Non-templated wrapper class
class FooWrapper {
private:
IFoo *pFoo;
public:
FooWrapper(int type) : pFoo(CreateFooT(type)) { assert(pFoo != nullptr); }
~FooWrapper() { delete pFoo; }
bool functionA() { return pFoo->functionA(); }
template <typename T> bool functionB(T arg)
{
if(pFoo->type() != TypeT<T>())
{
assert(pFoo->type() == TypeT<T>());
return false;
}
return static_cast<typename FooT<T>*>(pFoo)->functionB(arg);
}
// fun stuff:
// (const pendants omitted for readability)
bool changeType(int type)
{
delete pFoo;
pFoo = CreateFooT(type);
return pFoo != nullptr;
}
IFoo* Interface() { return pFoo; }
IFoo* operator->() { return pFoo; }
operator IFoo&() { return *pFoo; }
template <typename T> FooT<T> *InterfaceT()
{
if(pFoo->type() != TypeT<T>())
{
assert(pFoo->type() == TypeT<T>());
return nullptr;
}
return static_cast<typename FooT<T>*>(pFoo);
}
};
int main(int argc, char *argv[])
{
FooWrapper w1(TypeT<bool>());
FooWrapper w2(TypeT<double>());
w1.functionA(); // ok
w2.functionA(); // ok
w1.functionB(true); // ok
w1.functionB(0.5); // runtime error!
w2.functionB(true); // runtime error!
w2.functionB(0.5); // ok
// fun stuff
w2.changeType(TypeT<bool>()); // older changes will be lost
w2.functionB(true); // -> now ok
w1.Interface()->functionA();
w1->functionA();
IFoo &iref = w1;
iref.functionA();
FooT<bool> *ref = w1.InterfaceT<bool>();
ref->functionB(true);
return 0;
}
It is of course your responsibility to call the functions with the correct types, but you can easily add some error handling.
当然,您有责任使用正确的类型调用函数,但您可以轻松添加一些错误处理。
#1
10
You are actually attempting the impossible.
你实际上是在尝试不可能的事。
The very heart of the matter is simple: virtual
and template
do not mix well.
事情的核心很简单:虚拟和模板混合不好。
-
template
is about compile-time code generation. You can think of it as some kind of type-aware macros + a few sprinkled tricks for meta programming. -
virtual
is about runtime decision, and this require some work.
模板是关于编译时代码生成的。您可以将其视为某种类型感知宏+一些用于元编程的技巧。
虚拟是关于运行时决策,这需要一些工作。
virtual
is usually implemented using a virtual tables (think of a table which lists the methods). The number of methods need be known at compile time and is defined in the base class.
virtual通常使用虚拟表实现(想象一下列出方法的表)。需要在编译时知道方法的数量,并在基类中定义。
However, with your requirement, we would need a virtual table of infinite size, containing methods for types we haven't seen yet and that will only be defined in the years to come... it's unfortunately impossible.
但是,根据您的要求,我们需要一个无限大小的虚拟表,其中包含我们尚未看到的类型的方法,并且只会在未来几年内定义......遗憾的是不可能。
And if it were possible ?
如果可能的话?
Well, it just would not make sense. What happens when I call Foo2
with an int
? It's not meant for it! Therefore it breaks the principle that Foo2
implements all the methods from IFoo
.
嗯,这没有意义。当我用int调用Foo2时会发生什么?它并不意味着它!因此,它打破了Foo2实现IFoo所有方法的原则。
So, it would be better if you stated the real problem, this way we could help you at a design level rather than at a technical level :)
所以,如果你说出真正的问题会更好,这样我们可以在设计层面而不是技术层面帮助你:)
#2
5
Easiest way is to make your interface templated.
最简单的方法是使您的界面模板化。
template <class T>
class IFoo {
public:
virtual void functionA()=0;
virtual void functionB(T arg){ do something; };
};
template<class T>
class Foo : public IFoo<T>{
public:
void functionA(){ do something; };
void functionB(T arg){ do something; };
};
#3
4
Since functionB's argument type must be known in advance, you have only one choice: Make it a type which can hold every possible argument. This is sometimes called a "top type" and the boost libraries have the any
type which gets quite close to what a top type would do. Here is what could work:
由于必须事先知道functionB的参数类型,因此您只有一个选择:使其成为可以包含所有可能参数的类型。这有时被称为“*类型”,并且boost库具有与*类型相近的任何类型。这是可行的:
#include <boost/any.hpp>
#include <iostream>
using namespace boost;
class IFoo {
public:
virtual void functionA()=0;
virtual void functionB(any arg)=0; //<-can hold almost everything
};
template<class T>
class Foo : public IFoo{
public:
void functionA(){ };
void real_functionB(T arg)
{
std::cout << arg << std::endl;
};
// call the real functionB with the actual value in arg
// if there is no T in arg, an exception is thrown!
virtual void functionB(any arg)
{
real_functionB(any_cast<T>(arg));
}
};
int main()
{
Foo<int> f_int;
IFoo &if_int=f_int;
if_int.functionB(10);
Foo<double> f_double;
IFoo &if_double=f_double;
if_int.functionB(10.0);
}
Unfortunately, any_cast
does not know about the usual conversions. For example any_cast<double>(any(123))
throws an exception, because it does not even try to convert the integer 123 to a double. If does not care about conversions, because it is impossible to replicate all of them anyway. So there are a couple of limitations, but it is possible to find workarounds if necessary.
不幸的是,any_cast不知道通常的转换。例如,any_cast
#4
0
I don't think you can get what you want. Think of this if you were to implement your suggestion: if you have a pointer to an IFoo
instance and you call functionB()
, what type parameter should you give it? The underlying problem is that Foo1::functionB
and Foo2::functionB
have different signatures and do different things.
我认为你不能得到你想要的东西。如果你要实现你的建议,可以考虑一下:如果你有一个指向IFoo实例的指针而你调用了functionB(),你应该给它什么类型的参数?根本问题是Foo1 :: functionB和Foo2 :: functionB有不同的签名并做不同的事情。
#5
0
You can achieve something comparable by wrapping the IFoo* pointer in a class and exposing the functionality via generic template functions of the non-templated wrapper class:
通过将IFoo *指针包装在类中并通过非模板化包装类的通用模板函数公开功能,可以实现类似的功能:
#include <assert.h>
// interface class
class IFoo {
public:
virtual int type() const = 0; // return an identifier for the template parameter
virtual bool functionA() = 0;
};
// This function returns a unique identifier for each supported T
template <typename T> static int TypeT() { static_assert("not specialized yet"); }
template <> static int TypeT<bool>() { return 0; }
template <> static int TypeT<double>() { return 1; }
//template <> static int TypeT<...>() { ... }
// templated class
template <typename T> class FooT : public IFoo {
public:
int type() const override { return TypeT<T>(); }
bool functionA() override { return true; }
// not in interface
bool functionB(T arg) { return arg == T(); }
};
// function to create an instance of FooT (could also be static function in FooT)
static IFoo* CreateFooT(int type)
{
switch (type)
{
case 0: return new FooT<bool>();
case 1: return new FooT<double>();
//case ...: return new FooT<...>();
default: return nullptr;
}
}
// Non-templated wrapper class
class FooWrapper {
private:
IFoo *pFoo;
public:
FooWrapper(int type) : pFoo(CreateFooT(type)) { assert(pFoo != nullptr); }
~FooWrapper() { delete pFoo; }
bool functionA() { return pFoo->functionA(); }
template <typename T> bool functionB(T arg)
{
if(pFoo->type() != TypeT<T>())
{
assert(pFoo->type() == TypeT<T>());
return false;
}
return static_cast<typename FooT<T>*>(pFoo)->functionB(arg);
}
// fun stuff:
// (const pendants omitted for readability)
bool changeType(int type)
{
delete pFoo;
pFoo = CreateFooT(type);
return pFoo != nullptr;
}
IFoo* Interface() { return pFoo; }
IFoo* operator->() { return pFoo; }
operator IFoo&() { return *pFoo; }
template <typename T> FooT<T> *InterfaceT()
{
if(pFoo->type() != TypeT<T>())
{
assert(pFoo->type() == TypeT<T>());
return nullptr;
}
return static_cast<typename FooT<T>*>(pFoo);
}
};
int main(int argc, char *argv[])
{
FooWrapper w1(TypeT<bool>());
FooWrapper w2(TypeT<double>());
w1.functionA(); // ok
w2.functionA(); // ok
w1.functionB(true); // ok
w1.functionB(0.5); // runtime error!
w2.functionB(true); // runtime error!
w2.functionB(0.5); // ok
// fun stuff
w2.changeType(TypeT<bool>()); // older changes will be lost
w2.functionB(true); // -> now ok
w1.Interface()->functionA();
w1->functionA();
IFoo &iref = w1;
iref.functionA();
FooT<bool> *ref = w1.InterfaceT<bool>();
ref->functionB(true);
return 0;
}
It is of course your responsibility to call the functions with the correct types, but you can easily add some error handling.
当然,您有责任使用正确的类型调用函数,但您可以轻松添加一些错误处理。