0

我有一个抽象类型A和两个派生类型A1A2.
我想向 A 类添加一个方法 M,它采用 A 类型的参数。但是,我需要临时多态性。

确实,我需要 3 个实现:A1::M(A1 a),A1::M(A2 a)A2::(A1 a), A2::M(A2 a)。但是我想要一种抽象的方式来调用带有类型 A 的指针的方法 M。

我可以将所有签名声明放在 class 中A,但这很糟糕。

4

4 回答 4

2

使用模拟双重调度

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。)

我认为没有办法避免基类中的多个重载。

于 2012-06-20T08:27:37.683 回答
0

为什么不这样做呢?

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 做同样的事情?

于 2012-06-20T08:25:08.183 回答
0

如果您想要多态行为 - 那么您所需要的只是基类 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"

我不认为这是你想要的行为

于 2012-06-20T08:20:10.260 回答
0

这是双重派送。当你写:

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带有一对 typeidas 索引的 , 。您不能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的他们。(一种常见的解决方案是使它们成为其中一个类的静态成员,并让每个派生类定义一个类型的静态成员,该类型负责注册其负责的所有函数。)

于 2012-06-20T08:44:56.360 回答