我有一个抽象类型A
和两个派生类型A1
和A2
.
我想向 A 类添加一个方法 M,它采用 A 类型的参数。但是,我需要临时多态性。
确实,我需要 3 个实现:A1::M(A1 a)
,A1::M(A2 a)
和A2::(A1 a)
, A2::M(A2 a)
。但是我想要一种抽象的方式来调用带有类型 A 的指针的方法 M。
我可以将所有签名声明放在 class 中A
,但这很糟糕。
我有一个抽象类型A
和两个派生类型A1
和A2
.
我想向 A 类添加一个方法 M,它采用 A 类型的参数。但是,我需要临时多态性。
确实,我需要 3 个实现:A1::M(A1 a)
,A1::M(A2 a)
和A2::(A1 a)
, A2::M(A2 a)
。但是我想要一种抽象的方式来调用带有类型 A 的指针的方法 M。
我可以将所有签名声明放在 class 中A
,但这很糟糕。
使用模拟双重调度。
class A {
public:
virtual void M(A &) = 0;
virtual void M(A1 &) = 0;
virtual void M(A2 &) = 0;
};
class A1 : public A {
public:
virtual void M(A &a) { a.M(*this); }
virtual void M(A1 &a) { std::cout << "A1 <- A1\n"; }
virtual void M(A2 &a) { std::cout << "A2 <- A1\n"; }
};
class A2 : public A {
public:
virtual void M(A &a) { a.M(*this); }
virtual void M(A1 &a) { std::cout << "A1 <- A2\n"; }
virtual void M(A2 &a) { std::cout << "A2 <- A2\n"; }
};
(参见例如http://ideone.com/nycls。)
我认为没有办法避免基类中的多个重载。
为什么不这样做呢?
void A1::M( A a )
{
if( dynamic_cast< A1* >( &a ) )
{
// do your A1::M1(A1 a) stuff
}
else
if( dynamic_cast< A2* >( &a ) )
{
// do your A1::M2(A2 a) stuff
}
else
{
throw std::logic_error( "Unsupported A type." );
}
}
并为 A2::M 做同样的事情?
如果您想要多态行为 - 那么您所需要的只是基类 A 中的一个方法。然后您可以在 A1、A2 中重新实现该方法。
之后你可以写:
A *a1 = new A1();
A *a2 = new A2();
a1->M(a2); //polymorphic behavior
如果你做这样的事情:
struct A
{
virtual void M(A *a) {}
};
struct A1 : public A
{
virtual void M(A1 *a) {cout << "A1" << endl;}
virtual void M(A *a) {cout << "A" << endl;}
};
然后:
A1 * a1 = new A1();
a1->M(a1); //prints "A1"
A * a = a1;
a->M(a1); //prints "A"
我不认为这是你想要的行为
这是双重派送。当你写:
A* p1;
A* p2;
p1->M(*p2);
应该同时调度 of*p1
的类型和类型*p2
。
在开始之前,您必须意识到这意味着不同派生类型n^2
的函数
。n
并且在某个地方,必须有人知道所有派生类型(除非您可以为未知的类型对定义某种“默认”实现)。
有两种实现方式。最简单的,如果层次结构是封闭的(即客户端代码不能引入新的派生类)确实在基类中使用了许多虚函数——通常是受保护的,因为它们不是为了在层次结构之外调用而设计的:
// Forward references needed for all derived classes...
class A1;
class A2;
// ...
class A
{
protectd:
virtual void doM(A1* arg) = 0;
virtual void doM(A2* arg) = 0;
// ...
public:
virtual void M(A& arg) = 0;
};
在派生类中,实现M
总是相同的:
void A1::M(A& arg)
{
arg.doM( this );
}
这很简单,而且相对有效,但是每次添加新的派生类时都需要更改抽象基类和所有派生类(必须实现新的虚函数)。但是,它对于封闭的层次结构很有用;我已经在使用策略模式的类中将其用于部分行为,其中各种策略都在源文件中定义,并且不暴露给客户端(并且策略的抽象基础仅在标头中前向声明,因此如果我添加策略,则无需更改标题)。
更通用的解决方案将涉及std::map
带有一对
typeid
as 索引的 , 。您不能typeid
直接使用,因为它不可复制。C++11 提供了一个type_index
包装它;如果您使用的是较旧的编译器,那么自己实现一个相当简单。基本原则类似于(可能在 A 本身):
typedef std::pair<std::type_index, std::type_index> TypePairKey;
typedef void (*FuncPtr)( M* arg1, M* arg2 );
typedef std::unordered_map<TypePairKey, FuncPtr> DispatchMap;
static DispatchMap ourDispatchMap;
和:
void M( A& arg ) // NOT virtual !!!
{
DispatchMap::iterator entry
= ourDispatchMap.find(
DispatchMap::value_type( typeid( *this ), typeid( arg ) ) );
assert( entry != ourDispatchMap.end() );
// Or some default handling, maybe throw NotYetImplemented()
(*entry->second)( this, &arg );
}
真正的问题在于编写每个单独的函数并将它们的地址插入到地图中(在第一次使用之前)。当然,函数本身可以使用dynamic_cast
,甚至static_cast
,如果您可以确定它们只会从这里被调用,并且它们可以是所涉及的类的朋友,但仍然有
n 2的他们。(一种常见的解决方案是使它们成为其中一个类的静态成员,并让每个派生类定义一个类型的静态成员,该类型负责注册其负责的所有函数。)