我之前问了一个问题,但事实证明我的问题没有被我的例子正确建模。所以这是我的实际问题:
- 我有类
A
,类B
继承自A
, - 我有两个功能
foo(A&)
和foo(B&)
, - 我有一个
A*
指针列表,其中包含A
和B
的实例。 - 如何为
A
实例调用foo(A&)
,为B
实例调用foo(B&)
?约束:我可以修改A
并B
实现,但不能修改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();
}