C++ C++&引用;“虚拟参数”;

C++ C++&引用;“虚拟参数”;,c++,virtual-functions,C++,Virtual Functions,考虑以下代码: 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 voi

考虑以下代码:

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。我正在寻找一种能够执行AA代码的方法


其动机是,这是为了一个代码库,最终用户通常必须编写BB形式的类,我希望这个过程尽可能简单(即,用户不必使用RTTI来确定他们处理的是哪个派生类)。任何想法(和Voodoo从任何版本的C++标准)都是赞赏的。

< P>你可以使用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
    }
};
B类{
空f_基地(施工图A&A){
//密码
}
公众:
虚空f(常数A和A){
f_base(a);//将基本机箱移到外部
//隔离调度机制。
}
};
BB类:公共B类{
公众:
虚空f(常数A和A){
常数AA*pAA=动态施法(&a);
国际单项体育联合会(pAA){
f(*pAA);
返回;
}
f_基地(a);
}
无效f(常数AA&a){
//机管局代码
}
};

关于这种类型的分派以及使用模板的更好的打包的更多讨论将在此处演示:

您正在寻找的概念称为双重分派。阅读更多关于它的网页

<>它不是内置于C++中,而是有多种方法来模仿它。其中一个是访问者模式,也可以在上面的链接中找到


但是,你可能会发现所有的方法都缺乏优雅,因为你需要把AB引入到B,BB到A,或者使用RTTI和CasStudio。

< P>你需要类似于被称为C++的机制的东西,这在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);
    }
};

这是一种糟糕的风格,但你似乎受到了一些严格的限制

#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
}
#包括
//其他一切未经修改
BB类:公共B类{
公众:
虚空f(常量A和参数)覆盖{
尝试{//尝试转换为AA&
常数AA&a{dynamic_cast(arg)};
//机管局代码
}catch(std::bad_cast){//如果失败,将其作为A传递给B::f&
这->B::f(arg);
}
}
};
////////////////////////////////////////
int main(){
A*aa=新aa;
A*A=新的A;
B*B=新的BB;
b->f(*a);//执行的
b->f(*aa);//执行aa的代码
}
根据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
    }
};
BB类:公共B类{
公众:
虚空f(常量A和参数)覆盖{
常数AA*ap=动态施法(&arg);
如果(ap==nullptr){
返回此->B::f(arg);
}
const AA&a{*ap};//如果需要,请获取引用
//机管局代码
}
};

可能类似于
b->*(BB::&f)(*a)
甚至是显式转换
static_cast(b->f)(*a)
?问问自己,你是否真的需要将a
AA
的引用传递给
BB::f()
而不仅仅是传递一个
a
的引用。反之亦然。访客模式?(取决于从
A
派生的类的数量,以及集合更改的频率。)@AlanStokes最终访问者模式+一个聪明的模板元编程机制?@par:对,所以将对
A
的引用传递给
BB
,然后从
BB
调用
A
上的方法,该方法将动态绑定到
AA
实例上的方法。我仍然不明白为什么在您的用例中这是不可能的。不幸的是,我不能将BB介绍给AA和viceversa@par你“不能介绍”是什么意思?转发声明不起作用还是什么?@πάνταῥεῖ: 也许其中一些类型是他的库的一部分,而其他类型是用户添加的?@Deduplicator:很遗憾,我不能将BB介绍给AA,反之亦然+1谢谢!我认为这是一个选项(相当于使用RTTI,然后使用动态强制转换),但如果可能的话,我想避免它。
dynamic\u cast
on pointers在失败时返回null,而不是抛出,这几乎可以肯定是更快的-这在您期望它发生时很重要。为什么
ap==nullptr
而不是
!ap
?@Deduplicator一个可以说比另一个更明确/表达,但这并不重要,不是吗。@我想每个人都有自己的重复数据消除工具。如果我觉得有什么意义的话,我从不介意输入更多的字符。我只是对这一点没有强烈的感觉。里面似乎没有太多的
typeid
。@AlanStokes
typeid
dynamic\u cast
是同一个豆荚里的两个馅饼。很好的malaprism;-)。但吊舱通常被称为RTTI。(有一个不包含X的X的例子听起来有点奇怪。)@AlanStokes:一些模板的泛化使用这个精确的操作符对候选列表进行排序。不过,我还是改了,这样就不会有人绊倒了。