【问题标题】:C++ "Virtual arguments"C++“虚拟参数”
【发布时间】:2014-05-05 18:47:58
【问题描述】:

考虑以下代码:

class A {
public:
    virtual ~A() {}
};

class AA : public A {
};

////////////////////////////////////////

class B {
public:
    virtual void f(const A &a) {
        // code for A
    }
};

class BB : public B {
public:
    virtual void f(const AA &a) {
        // code for AA
    }
};

////////////////////////////////////////

int main() {
    A *a = new AA;
    B *b = new BB;

    b->f(*a);
}

显然,vtables 的构造使得当上面运行时,// code for A 被执行。我正在寻找一种能够代替// code for AA执行的方法。

动机是这是一个代码库,最终用户经常需要编写 BB 形式的类,我希望这个过程尽可能简单(即用户不必使用 RTTI 来确定他们正在处理的 A 的派生类)。任何想法(以及来自任何版本的 C++ 标准的巫术)都将受到赞赏。

【问题讨论】:

  • 可能是 b->*(BB::&f)(*a) 之类的东西,甚至是明确转换 static_cast<(void*)(const AA&)>(b->f)(*a)?
  • 问问自己,您是否真的需要将对AA 的引用传递给BB::f(),而不是仅仅传递对A 的引用。反之亦然。
  • 访客模式? (取决于有多少类派生自 A,以及集合更改的频率。)
  • @AlanStokes 最终访问者模式 + 巧妙的模板元编程机制?
  • @par: 对,所以将对A 的引用传递给BB,然后从BB 调用A 上的方法,这将动态绑定到实例上的方法AA。我仍然不明白为什么这在您的用例中是不可能的。

标签: c++ virtual-functions


【解决方案1】:

您可以使用 RTTI 并为此明确地进行自己的调度。

不幸的是,协变类型仅适用于返回类型。

例子:

class B {
    void f_base(const A &a) {
        // code for A
    }
public:
    virtual void f(const A &a) {
        f_base(a); // Moved the base case outside
                   // to isolate the dispatching mechanism.
    }
};

class BB : public B {
public:
    virtual void f(const A& a) {
        const AA* pAA = dynamic_cast<const AA*>(&a);
        if(pAA) {
            f(*pAA);
            return;
        }
        f_base(a);
    }
    void f(const AA &a) {
        // code for AA
    }
};

这里展示了关于这种类型的调度以及更好的模板打包的更多讨论:"type-switch" construct in C++11

【讨论】:

  • 那里似乎没有多少typeid
  • @AlanStokes typeiddynamic_cast 是来自同一个 pod 的两个派。
  • 不错的不当行为 ;-)。但 pod 通常称为 RTTI。 (如果有一个不包含 X 的 X 示例,这听起来有点奇怪。)
  • @AlanStokes:模板的一些概括使用这个精确的运算符来对候选列表进行排序。不过,我改变了它,所以没有人会绊倒。
【解决方案2】:

您需要类似于double dispatch 的机制,在 C++ 中只能间接实现:

class A {
public:
    virtual callF(B & b) {
        b.f(this);
    }

    virtual callF(BB & b) {
        b.f(this);
    }
};

class AA : public A {
public:
    virtual callF(B & b) {
        b.f(this);
    }

    virtual callF(BB & b) {
        b.f(this);
    }
};

【讨论】:

  • 很遗憾,我无法将 BB 介绍给 AA,反之亦然
  • @par 你是什么意思,你'不能介绍'?前向声明不起作用还是什么?
  • @πάνταῥεῖ:也许有些类型是他图书馆的一部分,有些是用户添加的?
  • @Deduplicator: 完全正确
【解决方案3】:

这是一种可怕的风格,但您似乎受到了一些严格的限制

#include <typeinfo>

 // everything else unmodified 

class BB : public B {
public:
    virtual void f(const A& arg) override {
        try { //try to convert to an AA&
            const AA& a{dynamic_cast<const AA&>(arg)};
            // code for AA
        } catch (std::bad_cast) { // if it fails, pass it up to B::f as an A&
            this->B::f(arg);
        }
    }
};

////////////////////////////////////////

int main() {
    A *aa = new AA;
    A *a = new A;
    B *b = new BB;

    b->f(*a);  // code for A executed
    b->f(*aa); // code for AA executed
}

根据 Alan Stoke 的评论,动态转换指针在失败时要快得多,因此如果您预计经常失败,您也可以使用它:

class BB : public B {
public:
    virtual void f(const A& arg) override {
        const AA* ap = dynamic_cast<const AA*>(&arg);
        if (ap == nullptr) {
            return this->B::f(arg);
        }
        const AA& a{*ap}; // get a reference, if you want to
        // code for AA
    }
};

【讨论】:

  • +1 谢谢!我认为这是一种选择(相当于使用 RTTI,然后是动态转换),但我想尽可能避免它。
  • dynamic_cast on pointers 在失败时返回 null 而不是抛出,这几乎可以肯定更快 - 这在您期望它发生时很重要。
  • 为什么是ap == nullptr 而不是!ap
  • @Deduplicator 一个可以说比另一个更明确/更具表现力,但这并不重要,不是吗。
  • @Deduplicator 到每个他自己的我想。如果我觉得这意味着什么,我从不介意输入更多字符。我只是对这一点没有强烈的感觉。
【解决方案4】:

您正在寻找的概念称为双重调度。在http://en.wikipedia.org/wiki/Double_dispatch 上阅读更多相关信息

它不是内置在 C++ 中的,但有多种方法可以模拟它。其中之一是 访问者模式,也可以在上面的链接中找到。

但是,您可能会发现所有方法都缺乏优雅,因为您需要将 AA 引入 B,将 BB 引入 A,或者使用 RTTI 和强制转换。

【讨论】:

  • 很遗憾,我无法将 BB 介绍给 AA,反之亦然
猜你喜欢
  • 1970-01-01
  • 2010-09-21
  • 1970-01-01
  • 2012-04-18
  • 2019-01-01
  • 2017-02-04
  • 1970-01-01
  • 2012-09-04
  • 2017-06-10
相关资源
最近更新 更多