5

为了这个问题,这被大大简化了。假设我有一个层次结构:

struct Base {
    virtual int precision() const = 0;
};

template<int Precision>
struct Derived : public Base {

    typedef Traits<Precision>::Type Type;

    Derived(Type data) : value(data) {}
    virtual int precision() const { return Precision; }

    Type value;

};

我想要一个带有签名的非模板函数:

Base* function(const Base& a, const Base& b);

a其中函数结果的具体类型与b其中较大者的类型相同Precision;类似于以下伪代码:

Base* function(const Base& a, const Base& b) {

    if (a.precision() > b.precision())

        return new A( ((A&)a).value + A(b.value).value );

    else if (a.precision() < b.precision())

        return new B( B(((A&)a).value).value + ((B&)b).value );

    else

        return new A( ((A&)a).value + ((A&)b).value );

}

其中A和分别是和B的具体类型。我想独立于有多少实例进行操作。我想避免使用大量的比较表,尽管 RTTI 的答案很好。有任何想法吗?abfunctionDerivedtypeid()

4

3 回答 3

4

您所要求的称为多重调度,即多重方法。它不是 C++ 语言的特性。

有针对特殊情况的解决方法,但你不能避免自己做一些实现。

多重分派的一种常见模式称为“重新分派”,也称为“递归延迟分派”。基本上,一个虚拟方法解析一个参数类型,然后调用另一个虚拟方法,直到所有参数都被解析。外部的函数(如果有的话)只是调用这些虚方法中的第一个。

假设有 n 个派生类,将有一个方法来解析第一个参数,n 来解析第二个参数,n*n 来解析第三个等等 - 无论如何,最坏的情况。这是相当多的手动工作,并且使用基于 typeid 的条件块可能更容易进行初始开发,但使用重新调度对维护来说更健壮。

class Base;
class Derived1;
class Derived2;

class Base
{
  public:
    virtual void Handle (Base* p2);

    virtual void Handle (Derived1* p1);
    virtual void Handle (Derived2* p1);
};

class Derived1 : public Base
{
  public:
    void Handle (Base* p2);

    void Handle (Derived1* p1);
    void Handle (Derived2* p1);
};

void Derived1::Handle (Base* p2)
{
  p2->Handle (this);
}

void Derived1::Handle (Derived1* p1)
{
  //  p1 is Derived1*, this (p2) is Derived1*
}

void Derived1::Handle (Derived2* p1)
{
  //  p1 is Derived2*, this (p2) is Derived1*
}

//  etc

使用派生类的模板来实现这一点将很困难,并且处理它的模板元编程可能是不可读、不可维护且非常脆弱的。不过,使用非模板方法实现调度,然后使用 mixin 模板(一个将其基类作为模板参数的模板类)来扩展它并使用其他功能可能不是那么糟糕。

访问者设计模式与重调度 IIRC 密切相关(基本上使用实现)。

另一种方法是使用旨在处理问题的语言,并且有一些选项可以很好地与 C++ 配合使用。一种是使用treecc - 一种特定于域的语言,用于处理 AST 节点和多次调度操作,它与 lex 和 yacc 一样,生成“源代码”作为输出。

它为处理调度决策所做的只是根据 AST 节点 ID 生成 switch 语句——它可以很容易地成为动态类型的值类 ID,IYSWIM。但是,这些是您不必编写或维护的 switch 语句,这是一个关键的区别。我遇到的最大问题是 AST 节点的析构函数处理被篡改,这意味着除非您做出特殊努力,否则不会调用成员数据的析构函数 - 即它最适用于字段的 POD 类型。

另一种选择是使用支持多方法的语言预处理器。其中有一些,部分原因是 Stroustrup 确实有相当完善的想法来支持多方法。三坐标测量机就是其中之一。Doublecpp是另一个。还有一个是弗罗斯特项目。我相信 CMM 最接近 Stroustrup 所描述的,但我没有检查过。

不过,归根结底,多重分派只是做出运行时决策的一种方式,并且有很多方法可以处理相同的决策。专业的 DSL 带来了相当多的麻烦,因此您通常只在需要大量多次调度时才这样做。Redispatch 和访问者模式是强大的 WRT 维护,但代价是一些复杂性和混乱。简单的条件语句可能是简单情况的更好选择,但要注意在编译时检测未处理情况的可能性即使不是不可能也很困难。

通常情况下,没有一种正确的方法可以做到这一点,至少在 C++ 中是这样。

于 2010-03-12T23:47:34.730 回答
3

您不能将 function() 直接委托给模板化代码,而无需在所有可能类型的大量列表之间进行选择,因为模板在编​​译时扩展,而在编译时 function() 不知道它实际上将是什么派生类型被称为。operation您需要为需要的每个模板化函数版本编译模板化代码的实例,这可能是一个无限集。

按照这个逻辑,唯一知道所有可能需要的模板的地方就是Derived类本身。因此,您的Derived班级应该包括一个成员:

Derived<Precision> *operation(Base& arg2) {
  Derived<Precision> *ptr = new Derived<Precision>;
  // ...
  return ptr;
}

然后,您可以function像这样定义,并间接进行调度:

Base* function(const Base& a, const Base& b) {
  if (a.precision() > b.precision())
    return a.operation(b);
  else 
    return b.operation(a);
}

请注意,这是简化版;如果您的操作的参数不是对称的,您需要定义两个版本的成员函数——一个用this它代替第一个参数,一个用它代替第二个参数。

此外,这忽略了这样一个事实,即您需要某种方式a.operation来获得适当的形式,b.value而无需知道b. 您必须自己解决这个问题——请注意,(通过与前面相同的逻辑)不可能通过模板类型来解决这个问题b,因为您是在运行时调度的。解决方案取决于您拥有的确切类型,以及是否有某种方法可以让一种更高精度的类型在Derived不知道该对象的确切类型的情况下从相等或较低精度的对象中提取一个值。这可能是不可能的,在这种情况下,您会得到一长串类型 ID 的匹配项。

不过,您不必在 switch 语句中这样做。您可以为每种Derived类型提供一组成员函数,以便向上转换为更精确的函数。例如:

template<int i>
upCast<Derived<i> >() {
  return /* upcasted value of this */
}

然后,您的operator成员函数可以对 进行操作b.upcast<typeof(this)>,而不必显式地进行强制转换来获得所需类型的值。您可能必须显式实例化其中一些函数才能编译它们;我还没有对 RTTI 做足够的工作来肯定地说。

但是,从根本上说,问题在于,如果您有 N 个可能的精度,那么您就有 N N 个可能的组合,而这些组合中的每一个实际上都需要单独编译的代码。如果您不能在 的定义中使用模板function,那么您必须编译所有 N N 这些可能性的版本,并且您必须以某种方式告诉编译器生成它们,并且您必须以某种方式选择正确的在运行时调度到。使用成员函数的技巧消除了 N 的一个因素,但另一个因素仍然存在,并且没有办法使其完全通用。

于 2010-03-12T22:36:41.667 回答
1

首先,你想让你的precision成员成为一个static const int值,而不是一个函数,这样你就可以在编译时对其进行操作。在Derived中,它将是:

static const int precision = Precision;

然后,您需要一些辅助结构来确定最精确的基类/派生类。首先,您需要一个通用的 helper-helper 结构来根据布尔值选择两种类型之一:

template<typename T1, typename T2, bool use_first>
struct pickType {
  typedef T2 type;
};

template<typename T1, typename T2>
struct pickType<T1, T2, true> {
  typedef T1 type;
};

然后,pickType<T1, T2, use_first>::type将解析为T1if use_firstis true,否则解析为T2。所以,然后我们用它来选择最精确的类型:

template<typename T1, typename T2>
struct mostPrecise{
  typedef pickType<T1, T2, (T1::precision > T2::precision)>::type type;
};

现在,mostPrecise<T1, T2>::type将给您两种类型中的哪一种具有更大的precision价值。因此,您可以将函数定义为:

template<typename T1, typename T2>
mostPrecise<T1, T2>::type function(const T1& a, const T2& b) {
  // ...
}

你有它。

于 2010-03-12T22:13:36.933 回答