对于重载函数,为父实例和子实例调用专用版本



我之前问了一个问题,但事实证明我的问题没有被我的例子正确建模。所以这是我的实际问题:

  1. 我有类A,类B继承自A
  2. 我有两个功能foo(A&)foo(B&)
  3. 我有一个A*指针列表,其中包含AB的实例。
  4. 如何为A实例调用foo(A&),为B实例调用foo(B&)?约束:我可以修改AB实现,但不能修改foo的实现。

请参阅下面的示例:

#include <iostream>
#include <list>
class A {
public:
};
class B : public A {
public:
};
void bar(A &a) { std::cout << "This is an A" << std::endl; }
void bar(B &b) { std::cout << "This is a B" << std::endl; }
int main(int argc, char **argv) {
  std::list<A *> l;
  l.push_back(new B());
  l.push_back(new B());
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    bar(**it);
}

虽然我使用的是带有指针的容器,但bar是使用父类而不是子类的对象调用的:

# ./a.out
This is an A
This is an A
#

我期待

This is a B

将指针传递给bar(通过重写其签名(无济于事。

感谢安东尼奥帮助澄清问题。

由于重载是在编译时解决的,因此您需要为编译器提供足够的信息来决定要调用的bar的适当重载。由于您希望根据对象的运行时类型动态做出决定,因此虚函数将有很大帮助:

struct A {
    virtual void bar() { bar(*this); }
};
struct B : public A {
    virtual void bar() { bar(*this); }
};
看起来

实体是相同的,所以B::bar可以消除,但事实并非如此:尽管主体看起来完全相同,但由于C++中过载的静态分辨率,它们调用不同的bar

  • A::bar内部,*this的类型是A&的,所以第一次重载被称为。
  • B::bar内部,*this的类型是B&,所以调用第二个重载。

修改调用代码以调用成员bar将完成更改:

std::list<A *> l;
l.push_back(new B());
l.push_back(new B());
for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    (*it)->bar();

编辑:这回答了问题的第一个版本,现在看看dasblinkenlight的解决方案。


如果您这样做:

A* b = B();

然后*b将是 A 型。这就是你在循环中正在做的事情。这其中没有"虚拟性"或政治主义。

以下代码给出了您要查找的行为:

class A {
public:
virtual void bar() { std::cout << "This is an A" << std::endl; }
};
class B : public A {
public:
virtual void bar() { std::cout << "This is a B" << std::endl; }
};

int main(int argc, char **argv) {
  std::list<A *> l;
  l.push_back(new B());
  l.push_back(new B());
  l.push_back(new A());
  l.push_back(new B());
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    (*it)->bar();
}

以我上面的例子为例,在这种情况下:

b->bar();

将打印This is a b

您正在寻找运行时多态性。虚拟成员方法"自然"支持此功能。

另一种方法是使用 RTTI 并动态强制转换A*B*并在成功时调用bar......或者static_cast,如果你真的确定有B*对象。一般需要下投表示有问题的设计。

重要说明:运行时签入dynamic_cast无论如何都需要类型是多态的。也许您的特定A满足了这一点,但您无法更改类。如果没有,static_cast是唯一可用的选项。

如果你可以控制类,则可以使用标准多态性和重载机制,使用this上的虚拟方法作为"外部"调用的外观:

#include <iostream>
#include <list>
class A;
void external_bar(A&);
class A {
public:
virtual void bar() { external_bar(*this); };
};
class B;
void external_bar(B&); //IMPORTANT
class B : public A {
public:
virtual void bar() { external_bar(*this); };
};
void external_bar(A &a) { std::cout << "This is an A" << std::endl; }
void external_bar(B &b) { std::cout << "This is a B" << std::endl; }

int main(int argc, char **argv) {
  std::list<A *> l;
  l.push_back(new B());
  l.push_back(new B());
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    (*it)->bar();
}

这也有缺点。需要前向声明。而且你需要注意一切都被正确定义,因为如果你忘记了// IMPORTANT行,编译器会为A&选择external_bar的定义,因为它是隐式可转换的,你可能会在发现错误时感到非常头疼。

其他人已经解释了如何实现它。

我只限于为什么会这样。

B 在这里被隐式转换为 A。所以它目前只有 A 的属性。

向上投掷隐含在C++中。

仅当基类是多态时,才能在C++中向下转换。

简而言之,多态需求只不过是基类中可以被派生的东西覆盖!!虚拟方法

然后,您可以使用其他人规定的RTTI和dynamic_cast来执行此操作。

例:

#include <iostream>
#include <list>
class A {
public:
virtual void dummy() = 0;
};
class B : public A {
public:
void dummy() { }
};
void bar(A &a) { std::cout << "This is an A" <<  std::endl; }
void bar(B &b) { std::cout << "This is a B" << std::endl; }
int main(int argc, char **argv) {
  std::list<A *> l;
  l.push_back(new B());
  l.push_back(new B());
//Prints A
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    bar(**it); 
//Prints B
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    bar(dynamic_cast<B&>(**it));
}
Answer:
This is an A
This is an A
This is a B
This is a B

注意:仅当您的列表包含 B 类型的对象时,才会崩溃。这只能解释上层与下落

Antonio写了一个涉及虚函数的好解决方案。如果您出于某种原因确实不想使用虚函数,那么您可以在自由函数中使用dynamic_cast

#include <iostream>
#include <list>
struct A {
    virtual ~A() {}   // important
};
struct B : A {};
void bar(A &a) { std::cout << "This is an A" << std::endl; }
void bar(B &b) { std::cout << "This is a B" << std::endl; }
void bar_helper(A *ptr)
{
    if ( auto b = dynamic_cast<B *>(ptr) )
        bar(*b);
    else
        bar(*ptr);
}
int main()
{
    std::list<A *> ls;
    ls.push_back(new B);
    ls.push_back(new B);
    ls.push_back(new A);
    for (auto ptr : ls)
    {
        bar_helper(ptr);
        delete ptr;
    }
    ls.clear();
}

最新更新